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
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
.trace("Creating new shared sender key store.");
72 return new SenderKeySharedStore(new HashMap
<>(), objectMapper
, file
, addressResolver
, resolver
);
73 } catch (IOException e
) {
74 logger
.warn("Failed to load shared sender key store", e
);
75 throw new RuntimeException(e
);
79 private SenderKeySharedStore(
80 final Map
<UUID
, Set
<SenderKeySharedEntry
>> sharedSenderKeys
,
81 final ObjectMapper objectMapper
,
83 final RecipientAddressResolver addressResolver
,
84 final RecipientResolver resolver
86 this.sharedSenderKeys
= sharedSenderKeys
;
87 this.objectMapper
= objectMapper
;
89 this.addressResolver
= addressResolver
;
90 this.resolver
= resolver
;
93 public Set
<SignalProtocolAddress
> getSenderKeySharedWith(final DistributionId distributionId
) {
94 synchronized (sharedSenderKeys
) {
95 final var addresses
= sharedSenderKeys
.get(distributionId
.asUuid());
96 if (addresses
== null) {
99 return addresses
.stream()
100 .map(k
-> new SignalProtocolAddress(addressResolver
.resolveRecipientAddress(k
.recipientId())
101 .getIdentifier(), k
.deviceId()))
102 .collect(Collectors
.toSet());
106 public void markSenderKeySharedWith(
107 final DistributionId distributionId
, final Collection
<SignalProtocolAddress
> addresses
109 final var newEntries
= addresses
.stream()
110 .map(a
-> new SenderKeySharedEntry(resolveRecipient(a
.getName()), a
.getDeviceId()))
111 .collect(Collectors
.toSet());
113 synchronized (sharedSenderKeys
) {
114 final var previousEntries
= sharedSenderKeys
.getOrDefault(distributionId
.asUuid(), Set
.of());
116 sharedSenderKeys
.put(distributionId
.asUuid(), new HashSet
<>() {
118 addAll(previousEntries
);
126 public void clearSenderKeySharedWith(final Collection
<SignalProtocolAddress
> addresses
) {
127 final var entriesToDelete
= addresses
.stream()
128 .map(a
-> new SenderKeySharedEntry(resolveRecipient(a
.getName()), a
.getDeviceId()))
129 .collect(Collectors
.toSet());
131 synchronized (sharedSenderKeys
) {
132 for (final var distributionId
: sharedSenderKeys
.keySet()) {
133 final var entries
= sharedSenderKeys
.getOrDefault(distributionId
, Set
.of());
135 sharedSenderKeys
.put(distributionId
, new HashSet
<>(entries
) {
137 removeAll(entriesToDelete
);
145 public void deleteAll() {
146 synchronized (sharedSenderKeys
) {
147 sharedSenderKeys
.clear();
152 public void deleteAllFor(final RecipientId recipientId
) {
153 synchronized (sharedSenderKeys
) {
154 for (final var distributionId
: sharedSenderKeys
.keySet()) {
155 final var entries
= sharedSenderKeys
.getOrDefault(distributionId
, Set
.of());
157 sharedSenderKeys
.put(distributionId
, new HashSet
<>(entries
) {
159 removeIf(e
-> e
.recipientId().equals(recipientId
));
167 public void deleteAllFor(final DistributionId distributionId
) {
168 synchronized (sharedSenderKeys
) {
169 if (sharedSenderKeys
.remove(distributionId
.asUuid()) != null) {
175 public void mergeRecipients(RecipientId recipientId
, RecipientId toBeMergedRecipientId
) {
176 synchronized (sharedSenderKeys
) {
177 for (final var distributionId
: sharedSenderKeys
.keySet()) {
178 final var entries
= sharedSenderKeys
.getOrDefault(distributionId
, Set
.of());
180 sharedSenderKeys
.put(distributionId
,
182 .map(e
-> e
.recipientId
.equals(toBeMergedRecipientId
) ?
new SenderKeySharedEntry(
185 .collect(Collectors
.toSet()));
192 * @param identifier can be either a serialized uuid or a e164 phone number
194 private RecipientId
resolveRecipient(String identifier
) {
195 return resolver
.resolveRecipient(identifier
);
198 private void saveLocked() {
199 var storage
= new Storage(sharedSenderKeys
.entrySet().stream().flatMap(pair
-> {
200 final var sharedWith
= pair
.getValue();
201 return sharedWith
.stream()
202 .map(entry
-> new Storage
.SharedSenderKey(entry
.recipientId().id(),
204 pair
.getKey().toString()));
207 // Write to memory first to prevent corrupting the file in case of serialization errors
208 try (var inMemoryOutput
= new ByteArrayOutputStream()) {
209 objectMapper
.writeValue(inMemoryOutput
, storage
);
211 var input
= new ByteArrayInputStream(inMemoryOutput
.toByteArray());
212 try (var outputStream
= new FileOutputStream(file
)) {
213 input
.transferTo(outputStream
);
215 } catch (Exception e
) {
216 logger
.error("Error saving shared sender key store file: {}", e
.getMessage());
220 private record Storage(List
<SharedSenderKey
> sharedSenderKeys
) {
222 private record SharedSenderKey(long recipientId
, int deviceId
, String distributionId
) {}
225 private record SenderKeySharedEntry(RecipientId recipientId
, int deviceId
) {}