1 package org
.asamk
.signal
.manager
.storage
.senderKeys
;
3 import com
.fasterxml
.jackson
.databind
.ObjectMapper
;
5 import org
.asamk
.signal
.manager
.helper
.RecipientAddressResolver
;
6 import org
.asamk
.signal
.manager
.storage
.Utils
;
7 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientId
;
8 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientResolver
;
9 import org
.slf4j
.Logger
;
10 import org
.slf4j
.LoggerFactory
;
11 import org
.whispersystems
.libsignal
.SignalProtocolAddress
;
12 import org
.whispersystems
.signalservice
.api
.push
.DistributionId
;
13 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
15 import java
.io
.ByteArrayInputStream
;
16 import java
.io
.ByteArrayOutputStream
;
18 import java
.io
.FileInputStream
;
19 import java
.io
.FileNotFoundException
;
20 import java
.io
.FileOutputStream
;
21 import java
.io
.IOException
;
22 import java
.util
.Collection
;
23 import java
.util
.HashMap
;
24 import java
.util
.HashSet
;
25 import java
.util
.List
;
28 import java
.util
.UUID
;
29 import java
.util
.stream
.Collectors
;
31 public class SenderKeySharedStore
{
33 private final static Logger logger
= LoggerFactory
.getLogger(SenderKeySharedStore
.class);
35 private final Map
<UUID
, Set
<SenderKeySharedEntry
>> sharedSenderKeys
;
37 private final ObjectMapper objectMapper
;
38 private final File file
;
40 private final RecipientResolver resolver
;
41 private final RecipientAddressResolver addressResolver
;
43 public static SenderKeySharedStore
load(
44 final File file
, final RecipientAddressResolver addressResolver
, final RecipientResolver resolver
45 ) throws IOException
{
46 final var objectMapper
= Utils
.createStorageObjectMapper();
47 try (var inputStream
= new FileInputStream(file
)) {
48 final var storage
= objectMapper
.readValue(inputStream
, Storage
.class);
49 final var sharedSenderKeys
= new HashMap
<UUID
, Set
<SenderKeySharedEntry
>>();
50 for (final var senderKey
: storage
.sharedSenderKeys
) {
51 final var recipientId
= resolver
.resolveRecipient(senderKey
.recipientId
);
52 if (recipientId
== null) {
55 final var entry
= new SenderKeySharedEntry(recipientId
, senderKey
.deviceId
);
56 final var distributionId
= UuidUtil
.parseOrNull(senderKey
.distributionId
);
57 if (distributionId
== null) {
58 logger
.warn("Read invalid distribution id from storage {}, ignoring", senderKey
.distributionId
);
61 var entries
= sharedSenderKeys
.get(distributionId
);
62 if (entries
== null) {
63 entries
= new HashSet
<>();
66 sharedSenderKeys
.put(distributionId
, entries
);
69 return new SenderKeySharedStore(sharedSenderKeys
, objectMapper
, file
, addressResolver
, resolver
);
70 } catch (FileNotFoundException e
) {
71 logger
.debug("Creating new shared sender key store.");
72 return new SenderKeySharedStore(new HashMap
<>(), objectMapper
, file
, addressResolver
, resolver
);
76 private SenderKeySharedStore(
77 final Map
<UUID
, Set
<SenderKeySharedEntry
>> sharedSenderKeys
,
78 final ObjectMapper objectMapper
,
80 final RecipientAddressResolver addressResolver
,
81 final RecipientResolver resolver
83 this.sharedSenderKeys
= sharedSenderKeys
;
84 this.objectMapper
= objectMapper
;
86 this.addressResolver
= addressResolver
;
87 this.resolver
= resolver
;
90 public Set
<SignalProtocolAddress
> getSenderKeySharedWith(final DistributionId distributionId
) {
91 synchronized (sharedSenderKeys
) {
92 final var addresses
= sharedSenderKeys
.get(distributionId
.asUuid());
93 if (addresses
== null) {
96 return addresses
.stream()
97 .map(k
-> new SignalProtocolAddress(addressResolver
.resolveRecipientAddress(k
.recipientId())
98 .getIdentifier(), k
.deviceId()))
99 .collect(Collectors
.toSet());
103 public void markSenderKeySharedWith(
104 final DistributionId distributionId
, final Collection
<SignalProtocolAddress
> addresses
106 final var newEntries
= addresses
.stream()
107 .map(a
-> new SenderKeySharedEntry(resolveRecipient(a
.getName()), a
.getDeviceId()))
108 .collect(Collectors
.toSet());
110 synchronized (sharedSenderKeys
) {
111 final var previousEntries
= sharedSenderKeys
.getOrDefault(distributionId
.asUuid(), Set
.of());
113 sharedSenderKeys
.put(distributionId
.asUuid(), new HashSet
<>() {
115 addAll(previousEntries
);
123 public void clearSenderKeySharedWith(final Collection
<SignalProtocolAddress
> addresses
) {
124 final var entriesToDelete
= addresses
.stream()
125 .map(a
-> new SenderKeySharedEntry(resolveRecipient(a
.getName()), a
.getDeviceId()))
126 .collect(Collectors
.toSet());
128 synchronized (sharedSenderKeys
) {
129 for (final var distributionId
: sharedSenderKeys
.keySet()) {
130 final var entries
= sharedSenderKeys
.getOrDefault(distributionId
, Set
.of());
132 sharedSenderKeys
.put(distributionId
, new HashSet
<>(entries
) {
134 removeAll(entriesToDelete
);
142 public void deleteAll() {
143 synchronized (sharedSenderKeys
) {
144 sharedSenderKeys
.clear();
149 public void deleteAllFor(final RecipientId recipientId
) {
150 synchronized (sharedSenderKeys
) {
151 for (final var distributionId
: sharedSenderKeys
.keySet()) {
152 final var entries
= sharedSenderKeys
.getOrDefault(distributionId
, Set
.of());
154 sharedSenderKeys
.put(distributionId
, new HashSet
<>(entries
) {
156 removeIf(e
-> e
.recipientId().equals(recipientId
));
164 public void deleteAllFor(final DistributionId distributionId
) {
165 synchronized (sharedSenderKeys
) {
166 sharedSenderKeys
.remove(distributionId
.asUuid());
171 public void mergeRecipients(RecipientId recipientId
, RecipientId toBeMergedRecipientId
) {
172 synchronized (sharedSenderKeys
) {
173 for (final var distributionId
: sharedSenderKeys
.keySet()) {
174 final var entries
= sharedSenderKeys
.getOrDefault(distributionId
, Set
.of());
176 sharedSenderKeys
.put(distributionId
,
178 .map(e
-> e
.recipientId
.equals(toBeMergedRecipientId
) ?
new SenderKeySharedEntry(
181 .collect(Collectors
.toSet()));
188 * @param identifier can be either a serialized uuid or a e164 phone number
190 private RecipientId
resolveRecipient(String identifier
) {
191 return resolver
.resolveRecipient(identifier
);
194 private void saveLocked() {
195 var storage
= new Storage(sharedSenderKeys
.entrySet().stream().flatMap(pair
-> {
196 final var sharedWith
= pair
.getValue();
197 return sharedWith
.stream()
198 .map(entry
-> new Storage
.SharedSenderKey(entry
.recipientId().id(),
200 pair
.getKey().toString()));
203 // Write to memory first to prevent corrupting the file in case of serialization errors
204 try (var inMemoryOutput
= new ByteArrayOutputStream()) {
205 objectMapper
.writeValue(inMemoryOutput
, storage
);
207 var input
= new ByteArrayInputStream(inMemoryOutput
.toByteArray());
208 try (var outputStream
= new FileOutputStream(file
)) {
209 input
.transferTo(outputStream
);
211 } catch (Exception e
) {
212 logger
.error("Error saving shared sender key store file: {}", e
.getMessage());
216 private record Storage(List
<SharedSenderKey
> sharedSenderKeys
) {
218 private record SharedSenderKey(long recipientId
, int deviceId
, String distributionId
) {}
221 private record SenderKeySharedEntry(RecipientId recipientId
, int deviceId
) {}