1 package org
.asamk
.signal
.manager
.storage
.sessions
;
3 import org
.asamk
.signal
.manager
.api
.Pair
;
4 import org
.asamk
.signal
.manager
.helper
.RecipientAddressResolver
;
5 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientId
;
6 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientResolver
;
7 import org
.asamk
.signal
.manager
.util
.IOUtils
;
8 import org
.signal
.libsignal
.protocol
.state
.SessionRecord
;
9 import org
.slf4j
.Logger
;
10 import org
.slf4j
.LoggerFactory
;
11 import org
.whispersystems
.signalservice
.api
.push
.ServiceId
;
14 import java
.io
.FileInputStream
;
15 import java
.io
.IOException
;
16 import java
.nio
.file
.Files
;
17 import java
.util
.Arrays
;
18 import java
.util
.Collection
;
19 import java
.util
.List
;
20 import java
.util
.Objects
;
21 import java
.util
.regex
.Matcher
;
22 import java
.util
.regex
.Pattern
;
24 public class LegacySessionStore
{
26 private final static Logger logger
= LoggerFactory
.getLogger(LegacySessionStore
.class);
28 public static void migrate(
29 final File sessionsPath
,
30 final RecipientResolver resolver
,
31 final RecipientAddressResolver addressResolver
,
32 final SessionStore sessionStore
34 final var keys
= getKeysLocked(sessionsPath
, resolver
);
35 final var sessions
= keys
.stream().map(key
-> {
36 final var record = loadSessionLocked(key
, sessionsPath
);
37 final var uuid
= addressResolver
.resolveRecipientAddress(key
.recipientId
).uuid();
38 if (record == null || uuid
.isEmpty()) {
41 return new Pair
<>(new SessionStore
.Key(ServiceId
.from(uuid
.get()), key
.deviceId()), record);
42 }).filter(Objects
::nonNull
).toList();
43 sessionStore
.addLegacySessions(sessions
);
44 deleteAllSessions(sessionsPath
);
47 private static void deleteAllSessions(File sessionsPath
) {
48 final var files
= sessionsPath
.listFiles();
53 for (var file
: files
) {
55 Files
.delete(file
.toPath());
56 } catch (IOException e
) {
57 logger
.error("Failed to delete session file {}: {}", file
, e
.getMessage());
61 Files
.delete(sessionsPath
.toPath());
62 } catch (IOException e
) {
63 logger
.error("Failed to delete session directory {}: {}", sessionsPath
, e
.getMessage());
67 private static Collection
<Key
> getKeysLocked(File sessionsPath
, final RecipientResolver resolver
) {
68 final var files
= sessionsPath
.listFiles();
72 return parseFileNames(files
, resolver
);
75 static final Pattern sessionFileNamePattern
= Pattern
.compile("(\\d+)_(\\d+)");
77 private static List
<Key
> parseFileNames(final File
[] files
, final RecipientResolver resolver
) {
78 return Arrays
.stream(files
)
79 .map(f
-> sessionFileNamePattern
.matcher(f
.getName()))
80 .filter(Matcher
::matches
)
82 final var recipientId
= resolver
.resolveRecipient(Long
.parseLong(matcher
.group(1)));
83 if (recipientId
== null) {
86 return new Key(recipientId
, Integer
.parseInt(matcher
.group(2)));
88 .filter(Objects
::nonNull
)
92 private static File
getSessionFile(Key key
, final File sessionsPath
) {
94 IOUtils
.createPrivateDirectories(sessionsPath
);
95 } catch (IOException e
) {
96 throw new AssertionError("Failed to create sessions path", e
);
98 return new File(sessionsPath
, key
.recipientId().id() + "_" + key
.deviceId());
101 private static SessionRecord
loadSessionLocked(final Key key
, final File sessionsPath
) {
102 final var file
= getSessionFile(key
, sessionsPath
);
103 if (!file
.exists()) {
106 try (var inputStream
= new FileInputStream(file
)) {
107 return new SessionRecord(inputStream
.readAllBytes());
108 } catch (Exception e
) {
109 logger
.warn("Failed to load session, resetting session: {}", e
.getMessage());
114 record Key(RecipientId recipientId
, int deviceId
) {}