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
.stream
.Collectors
;
30 public class SenderKeySharedStore
{
32 private final static Logger logger
= LoggerFactory
.getLogger(SenderKeySharedStore
.class);
34 private final Map
<DistributionId
, Set
<SenderKeySharedEntry
>> sharedSenderKeys
;
36 private final ObjectMapper objectMapper
;
37 private final File file
;
39 private final RecipientResolver resolver
;
40 private final RecipientAddressResolver addressResolver
;
42 public static SenderKeySharedStore
load(
43 final File file
, final RecipientAddressResolver addressResolver
, final RecipientResolver resolver
44 ) throws IOException
{
45 final var objectMapper
= Utils
.createStorageObjectMapper();
46 try (var inputStream
= new FileInputStream(file
)) {
47 final var storage
= objectMapper
.readValue(inputStream
, Storage
.class);
48 final var sharedSenderKeys
= new HashMap
<DistributionId
, Set
<SenderKeySharedEntry
>>();
49 for (final var senderKey
: storage
.sharedSenderKeys
) {
50 final var entry
= new SenderKeySharedEntry(RecipientId
.of(senderKey
.recipientId
), senderKey
.deviceId
);
51 final var uuid
= UuidUtil
.parseOrNull(senderKey
.distributionId
);
53 logger
.warn("Read invalid distribution id from storage {}, ignoring", senderKey
.distributionId
);
56 final var distributionId
= DistributionId
.from(uuid
);
57 var entries
= sharedSenderKeys
.get(distributionId
);
58 if (entries
== null) {
59 entries
= new HashSet
<>();
62 sharedSenderKeys
.put(distributionId
, entries
);
65 return new SenderKeySharedStore(sharedSenderKeys
, objectMapper
, file
, addressResolver
, resolver
);
66 } catch (FileNotFoundException e
) {
67 logger
.debug("Creating new shared sender key store.");
68 return new SenderKeySharedStore(new HashMap
<>(), objectMapper
, file
, addressResolver
, resolver
);
72 private SenderKeySharedStore(
73 final Map
<DistributionId
, Set
<SenderKeySharedEntry
>> sharedSenderKeys
,
74 final ObjectMapper objectMapper
,
76 final RecipientAddressResolver addressResolver
,
77 final RecipientResolver resolver
79 this.sharedSenderKeys
= sharedSenderKeys
;
80 this.objectMapper
= objectMapper
;
82 this.addressResolver
= addressResolver
;
83 this.resolver
= resolver
;
86 public Set
<SignalProtocolAddress
> getSenderKeySharedWith(final DistributionId distributionId
) {
87 synchronized (sharedSenderKeys
) {
88 return sharedSenderKeys
.get(distributionId
)
90 .map(k
-> new SignalProtocolAddress(addressResolver
.resolveRecipientAddress(k
.getRecipientId())
91 .getIdentifier(), k
.getDeviceId()))
92 .collect(Collectors
.toSet());
96 public void markSenderKeySharedWith(
97 final DistributionId distributionId
, final Collection
<SignalProtocolAddress
> addresses
99 final var newEntries
= addresses
.stream()
100 .map(a
-> new SenderKeySharedEntry(resolveRecipient(a
.getName()), a
.getDeviceId()))
101 .collect(Collectors
.toSet());
103 synchronized (sharedSenderKeys
) {
104 final var previousEntries
= sharedSenderKeys
.getOrDefault(distributionId
, Set
.of());
106 sharedSenderKeys
.put(distributionId
, new HashSet
<>() {
108 addAll(previousEntries
);
116 public void clearSenderKeySharedWith(final Collection
<SignalProtocolAddress
> addresses
) {
117 final var entriesToDelete
= addresses
.stream()
118 .map(a
-> new SenderKeySharedEntry(resolveRecipient(a
.getName()), a
.getDeviceId()))
119 .collect(Collectors
.toSet());
121 synchronized (sharedSenderKeys
) {
122 for (final var distributionId
: sharedSenderKeys
.keySet()) {
123 final var entries
= sharedSenderKeys
.getOrDefault(distributionId
, Set
.of());
125 sharedSenderKeys
.put(distributionId
, new HashSet
<>(entries
) {
127 removeAll(entriesToDelete
);
135 public void deleteAll() {
136 synchronized (sharedSenderKeys
) {
137 sharedSenderKeys
.clear();
142 public void deleteAllFor(final RecipientId recipientId
) {
143 synchronized (sharedSenderKeys
) {
144 for (final var distributionId
: sharedSenderKeys
.keySet()) {
145 final var entries
= sharedSenderKeys
.getOrDefault(distributionId
, Set
.of());
147 sharedSenderKeys
.put(distributionId
, new HashSet
<>(entries
) {
149 entries
.removeIf(e
-> e
.getRecipientId().equals(recipientId
));
157 public void mergeRecipients(RecipientId recipientId
, RecipientId toBeMergedRecipientId
) {
158 synchronized (sharedSenderKeys
) {
159 for (final var distributionId
: sharedSenderKeys
.keySet()) {
160 final var entries
= sharedSenderKeys
.getOrDefault(distributionId
, Set
.of());
162 sharedSenderKeys
.put(distributionId
,
164 .map(e
-> e
.recipientId
.equals(toBeMergedRecipientId
) ?
new SenderKeySharedEntry(
166 e
.getDeviceId()) : e
)
167 .collect(Collectors
.toSet()));
174 * @param identifier can be either a serialized uuid or a e164 phone number
176 private RecipientId
resolveRecipient(String identifier
) {
177 return resolver
.resolveRecipient(identifier
);
180 private void saveLocked() {
181 var storage
= new Storage(sharedSenderKeys
.entrySet().stream().flatMap(pair
-> {
182 final var sharedWith
= pair
.getValue();
183 return sharedWith
.stream()
184 .map(entry
-> new Storage
.SharedSenderKey(entry
.getRecipientId().getId(),
186 pair
.getKey().asUuid().toString()));
187 }).collect(Collectors
.toList()));
189 // Write to memory first to prevent corrupting the file in case of serialization errors
190 try (var inMemoryOutput
= new ByteArrayOutputStream()) {
191 objectMapper
.writeValue(inMemoryOutput
, storage
);
193 var input
= new ByteArrayInputStream(inMemoryOutput
.toByteArray());
194 try (var outputStream
= new FileOutputStream(file
)) {
195 input
.transferTo(outputStream
);
197 } catch (Exception e
) {
198 logger
.error("Error saving shared sender key store file: {}", e
.getMessage());
202 private static class Storage
{
204 public List
<SharedSenderKey
> sharedSenderKeys
;
206 // For deserialization
210 public Storage(final List
<SharedSenderKey
> sharedSenderKeys
) {
211 this.sharedSenderKeys
= sharedSenderKeys
;
214 private static class SharedSenderKey
{
216 public long recipientId
;
218 public String distributionId
;
220 // For deserialization
221 private SharedSenderKey() {
224 public SharedSenderKey(final long recipientId
, final int deviceId
, final String distributionId
) {
225 this.recipientId
= recipientId
;
226 this.deviceId
= deviceId
;
227 this.distributionId
= distributionId
;
232 private static final class SenderKeySharedEntry
{
234 private final RecipientId recipientId
;
235 private final int deviceId
;
237 public SenderKeySharedEntry(
238 final RecipientId recipientId
, final int deviceId
240 this.recipientId
= recipientId
;
241 this.deviceId
= deviceId
;
244 public RecipientId
getRecipientId() {
248 public int getDeviceId() {
253 public boolean equals(final Object o
) {
254 if (this == o
) return true;
255 if (o
== null || getClass() != o
.getClass()) return false;
257 final SenderKeySharedEntry that
= (SenderKeySharedEntry
) o
;
259 if (deviceId
!= that
.deviceId
) return false;
260 return recipientId
.equals(that
.recipientId
);
264 public int hashCode() {
265 int result
= recipientId
.hashCode();
266 result
= 31 * result
+ deviceId
;