1 package org
.asamk
.signal
.manager
.storage
.senderKeys
;
3 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientId
;
4 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientResolver
;
5 import org
.asamk
.signal
.manager
.util
.IOUtils
;
6 import org
.slf4j
.Logger
;
7 import org
.slf4j
.LoggerFactory
;
8 import org
.whispersystems
.libsignal
.SignalProtocolAddress
;
9 import org
.whispersystems
.libsignal
.groups
.state
.SenderKeyRecord
;
12 import java
.io
.FileInputStream
;
13 import java
.io
.FileOutputStream
;
14 import java
.io
.IOException
;
15 import java
.nio
.file
.Files
;
16 import java
.util
.Arrays
;
17 import java
.util
.HashMap
;
18 import java
.util
.List
;
20 import java
.util
.UUID
;
21 import java
.util
.regex
.Matcher
;
22 import java
.util
.regex
.Pattern
;
23 import java
.util
.stream
.Collectors
;
25 public class SenderKeyRecordStore
implements org
.whispersystems
.libsignal
.groups
.state
.SenderKeyStore
{
27 private final static Logger logger
= LoggerFactory
.getLogger(SenderKeyRecordStore
.class);
29 private final Map
<Key
, SenderKeyRecord
> cachedSenderKeys
= new HashMap
<>();
31 private final File senderKeysPath
;
33 private final RecipientResolver resolver
;
35 public SenderKeyRecordStore(
36 final File senderKeysPath
, final RecipientResolver resolver
38 this.senderKeysPath
= senderKeysPath
;
39 this.resolver
= resolver
;
43 public SenderKeyRecord
loadSenderKey(final SignalProtocolAddress address
, final UUID distributionId
) {
44 final var key
= getKey(address
, distributionId
);
46 synchronized (cachedSenderKeys
) {
47 return loadSenderKeyLocked(key
);
52 public void storeSenderKey(
53 final SignalProtocolAddress address
, final UUID distributionId
, final SenderKeyRecord
record
55 final var key
= getKey(address
, distributionId
);
57 synchronized (cachedSenderKeys
) {
58 storeSenderKeyLocked(key
, record);
62 public void deleteAll() {
63 synchronized (cachedSenderKeys
) {
64 cachedSenderKeys
.clear();
65 final var files
= senderKeysPath
.listFiles((_file
, s
) -> senderKeyFileNamePattern
.matcher(s
).matches());
70 for (final var file
: files
) {
72 Files
.delete(file
.toPath());
73 } catch (IOException e
) {
74 logger
.error("Failed to delete sender key file {}: {}", file
, e
.getMessage());
80 public void deleteAllFor(final RecipientId recipientId
) {
81 synchronized (cachedSenderKeys
) {
82 cachedSenderKeys
.clear();
83 final var keys
= getKeysLocked(recipientId
);
84 for (var key
: keys
) {
85 deleteSenderKeyLocked(key
);
90 public void mergeRecipients(RecipientId recipientId
, RecipientId toBeMergedRecipientId
) {
91 synchronized (cachedSenderKeys
) {
92 final var keys
= getKeysLocked(toBeMergedRecipientId
);
93 final var otherHasSenderKeys
= keys
.size() > 0;
94 if (!otherHasSenderKeys
) {
98 logger
.debug("Only to be merged recipient had sender keys, re-assigning to the new recipient.");
99 for (var key
: keys
) {
100 final var toBeMergedSenderKey
= loadSenderKeyLocked(key
);
101 deleteSenderKeyLocked(key
);
102 if (toBeMergedSenderKey
== null) {
106 final var newKey
= new Key(recipientId
, key
.getDeviceId(), key
.distributionId
);
107 final var senderKeyRecord
= loadSenderKeyLocked(newKey
);
108 if (senderKeyRecord
!= null) {
111 storeSenderKeyLocked(newKey
, senderKeyRecord
);
117 * @param identifier can be either a serialized uuid or a e164 phone number
119 private RecipientId
resolveRecipient(String identifier
) {
120 return resolver
.resolveRecipient(identifier
);
123 private Key
getKey(final SignalProtocolAddress address
, final UUID distributionId
) {
124 final var recipientId
= resolveRecipient(address
.getName());
125 return new Key(recipientId
, address
.getDeviceId(), distributionId
);
128 private List
<Key
> getKeysLocked(RecipientId recipientId
) {
129 final var files
= senderKeysPath
.listFiles((_file
, s
) -> s
.startsWith(recipientId
.getId() + "_"));
133 return parseFileNames(files
);
136 final Pattern senderKeyFileNamePattern
= Pattern
.compile("([0-9]+)_([0-9]+)_([0-9a-z\\-]+)");
138 private List
<Key
> parseFileNames(final File
[] files
) {
139 return Arrays
.stream(files
)
140 .map(f
-> senderKeyFileNamePattern
.matcher(f
.getName()))
141 .filter(Matcher
::matches
)
142 .map(matcher
-> new Key(RecipientId
.of(Long
.parseLong(matcher
.group(1))),
143 Integer
.parseInt(matcher
.group(2)),
144 UUID
.fromString(matcher
.group(3))))
145 .collect(Collectors
.toList());
148 private File
getSenderKeyFile(Key key
) {
150 IOUtils
.createPrivateDirectories(senderKeysPath
);
151 } catch (IOException e
) {
152 throw new AssertionError("Failed to create sender keys path", e
);
154 return new File(senderKeysPath
,
155 key
.getRecipientId().getId() + "_" + key
.getDeviceId() + "_" + key
.distributionId
.toString());
158 private SenderKeyRecord
loadSenderKeyLocked(final Key key
) {
160 final var senderKeyRecord
= cachedSenderKeys
.get(key
);
161 if (senderKeyRecord
!= null) {
162 return senderKeyRecord
;
166 final var file
= getSenderKeyFile(key
);
167 if (!file
.exists()) {
170 try (var inputStream
= new FileInputStream(file
)) {
171 final var senderKeyRecord
= new SenderKeyRecord(inputStream
.readAllBytes());
172 cachedSenderKeys
.put(key
, senderKeyRecord
);
173 return senderKeyRecord
;
174 } catch (IOException e
) {
175 logger
.warn("Failed to load sender key, resetting sender key: {}", e
.getMessage());
180 private void storeSenderKeyLocked(final Key key
, final SenderKeyRecord senderKeyRecord
) {
181 cachedSenderKeys
.put(key
, senderKeyRecord
);
183 final var file
= getSenderKeyFile(key
);
185 try (var outputStream
= new FileOutputStream(file
)) {
186 outputStream
.write(senderKeyRecord
.serialize());
188 } catch (IOException e
) {
189 logger
.warn("Failed to store sender key, trying to delete file and retry: {}", e
.getMessage());
191 Files
.delete(file
.toPath());
192 try (var outputStream
= new FileOutputStream(file
)) {
193 outputStream
.write(senderKeyRecord
.serialize());
195 } catch (IOException e2
) {
196 logger
.error("Failed to store sender key file {}: {}", file
, e2
.getMessage());
201 private void deleteSenderKeyLocked(final Key key
) {
202 cachedSenderKeys
.remove(key
);
204 final var file
= getSenderKeyFile(key
);
205 if (!file
.exists()) {
209 Files
.delete(file
.toPath());
210 } catch (IOException e
) {
211 logger
.error("Failed to delete sender key file {}: {}", file
, e
.getMessage());
215 private static final class Key
{
217 private final RecipientId recipientId
;
218 private final int deviceId
;
219 private final UUID distributionId
;
222 final RecipientId recipientId
, final int deviceId
, final UUID distributionId
224 this.recipientId
= recipientId
;
225 this.deviceId
= deviceId
;
226 this.distributionId
= distributionId
;
229 public RecipientId
getRecipientId() {
233 public int getDeviceId() {
237 public UUID
getDistributionId() {
238 return distributionId
;
242 public boolean equals(final Object o
) {
243 if (this == o
) return true;
244 if (o
== null || getClass() != o
.getClass()) return false;
246 final Key key
= (Key
) o
;
248 if (deviceId
!= key
.deviceId
) return false;
249 if (!recipientId
.equals(key
.recipientId
)) return false;
250 return distributionId
.equals(key
.distributionId
);
254 public int hashCode() {
255 int result
= recipientId
.hashCode();
256 result
= 31 * result
+ deviceId
;
257 result
= 31 * result
+ distributionId
.hashCode();