1 package org
.asamk
.signal
.manager
.storage
.protocol
;
3 import com
.fasterxml
.jackson
.core
.JsonGenerator
;
4 import com
.fasterxml
.jackson
.core
.JsonParser
;
5 import com
.fasterxml
.jackson
.databind
.DeserializationContext
;
6 import com
.fasterxml
.jackson
.databind
.JsonDeserializer
;
7 import com
.fasterxml
.jackson
.databind
.JsonNode
;
8 import com
.fasterxml
.jackson
.databind
.JsonSerializer
;
9 import com
.fasterxml
.jackson
.databind
.SerializerProvider
;
11 import org
.asamk
.signal
.manager
.util
.Utils
;
12 import org
.slf4j
.Logger
;
13 import org
.slf4j
.LoggerFactory
;
14 import org
.whispersystems
.libsignal
.SignalProtocolAddress
;
15 import org
.whispersystems
.libsignal
.protocol
.CiphertextMessage
;
16 import org
.whispersystems
.libsignal
.state
.SessionRecord
;
17 import org
.whispersystems
.signalservice
.api
.SignalServiceSessionStore
;
18 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
19 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
21 import java
.io
.IOException
;
22 import java
.util
.ArrayList
;
23 import java
.util
.Base64
;
24 import java
.util
.LinkedList
;
25 import java
.util
.List
;
27 class JsonSessionStore
implements SignalServiceSessionStore
{
29 private final static Logger logger
= LoggerFactory
.getLogger(JsonSessionStore
.class);
31 private final List
<SessionInfo
> sessions
= new ArrayList
<>();
33 private SignalServiceAddressResolver resolver
;
35 public JsonSessionStore() {
38 public void setResolver(final SignalServiceAddressResolver resolver
) {
39 this.resolver
= resolver
;
42 private SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
43 if (resolver
!= null) {
44 return resolver
.resolveSignalServiceAddress(identifier
);
46 return Utils
.getSignalServiceAddressFromIdentifier(identifier
);
51 public synchronized SessionRecord
loadSession(SignalProtocolAddress address
) {
52 var serviceAddress
= resolveSignalServiceAddress(address
.getName());
53 for (var info
: sessions
) {
54 if (info
.address
.matches(serviceAddress
) && info
.deviceId
== address
.getDeviceId()) {
56 return new SessionRecord(info
.sessionRecord
);
57 } catch (IOException e
) {
58 logger
.warn("Failed to load session, resetting session: {}", e
.getMessage());
59 return new SessionRecord();
64 return new SessionRecord();
67 public synchronized List
<SessionInfo
> getSessions() {
72 public synchronized List
<Integer
> getSubDeviceSessions(String name
) {
73 var serviceAddress
= resolveSignalServiceAddress(name
);
75 var deviceIds
= new LinkedList
<Integer
>();
76 for (var info
: sessions
) {
77 if (info
.address
.matches(serviceAddress
) && info
.deviceId
!= 1) {
78 deviceIds
.add(info
.deviceId
);
86 public synchronized void storeSession(SignalProtocolAddress address
, SessionRecord
record) {
87 var serviceAddress
= resolveSignalServiceAddress(address
.getName());
88 for (var info
: sessions
) {
89 if (info
.address
.matches(serviceAddress
) && info
.deviceId
== address
.getDeviceId()) {
90 if (!info
.address
.getUuid().isPresent() || !info
.address
.getNumber().isPresent()) {
91 info
.address
= serviceAddress
;
93 info
.sessionRecord
= record.serialize();
98 sessions
.add(new SessionInfo(serviceAddress
, address
.getDeviceId(), record.serialize()));
102 public synchronized boolean containsSession(SignalProtocolAddress address
) {
103 var serviceAddress
= resolveSignalServiceAddress(address
.getName());
104 for (var info
: sessions
) {
105 if (info
.address
.matches(serviceAddress
) && info
.deviceId
== address
.getDeviceId()) {
106 final SessionRecord sessionRecord
;
108 sessionRecord
= new SessionRecord(info
.sessionRecord
);
109 } catch (IOException e
) {
110 logger
.warn("Failed to check session: {}", e
.getMessage());
114 return sessionRecord
.hasSenderChain()
115 && sessionRecord
.getSessionVersion() == CiphertextMessage
.CURRENT_VERSION
;
122 public synchronized void deleteSession(SignalProtocolAddress address
) {
123 var serviceAddress
= resolveSignalServiceAddress(address
.getName());
124 sessions
.removeIf(info
-> info
.address
.matches(serviceAddress
) && info
.deviceId
== address
.getDeviceId());
128 public synchronized void deleteAllSessions(String name
) {
129 var serviceAddress
= resolveSignalServiceAddress(name
);
130 deleteAllSessions(serviceAddress
);
133 public synchronized void deleteAllSessions(SignalServiceAddress serviceAddress
) {
134 sessions
.removeIf(info
-> info
.address
.matches(serviceAddress
));
138 public void archiveSession(final SignalProtocolAddress address
) {
139 final var sessionRecord
= loadSession(address
);
140 if (sessionRecord
== null) {
143 sessionRecord
.archiveCurrentState();
144 storeSession(address
, sessionRecord
);
147 public void archiveAllSessions() {
148 for (var info
: sessions
) {
150 final var sessionRecord
= new SessionRecord(info
.sessionRecord
);
151 sessionRecord
.archiveCurrentState();
152 info
.sessionRecord
= sessionRecord
.serialize();
153 } catch (IOException ignored
) {
158 public static class JsonSessionStoreDeserializer
extends JsonDeserializer
<JsonSessionStore
> {
161 public JsonSessionStore
deserialize(
162 JsonParser jsonParser
, DeserializationContext deserializationContext
163 ) throws IOException
{
164 JsonNode node
= jsonParser
.getCodec().readTree(jsonParser
);
166 var sessionStore
= new JsonSessionStore();
168 if (node
.isArray()) {
169 for (var session
: node
) {
170 var sessionName
= session
.hasNonNull("name") ? session
.get("name").asText() : null;
171 if (UuidUtil
.isUuid(sessionName
)) {
172 // Ignore sessions that were incorrectly created with UUIDs as name
176 var uuid
= session
.hasNonNull("uuid") ? UuidUtil
.parseOrNull(session
.get("uuid").asText()) : null;
177 final var serviceAddress
= uuid
== null
178 ? Utils
.getSignalServiceAddressFromIdentifier(sessionName
)
179 : new SignalServiceAddress(uuid
, sessionName
);
180 final var deviceId
= session
.get("deviceId").asInt();
181 final var record = Base64
.getDecoder().decode(session
.get("record").asText());
182 var sessionInfo
= new SessionInfo(serviceAddress
, deviceId
, record);
183 sessionStore
.sessions
.add(sessionInfo
);
191 public static class JsonSessionStoreSerializer
extends JsonSerializer
<JsonSessionStore
> {
194 public void serialize(
195 JsonSessionStore jsonSessionStore
, JsonGenerator json
, SerializerProvider serializerProvider
196 ) throws IOException
{
197 json
.writeStartArray();
198 for (var sessionInfo
: jsonSessionStore
.sessions
) {
199 json
.writeStartObject();
200 if (sessionInfo
.address
.getNumber().isPresent()) {
201 json
.writeStringField("name", sessionInfo
.address
.getNumber().get());
203 if (sessionInfo
.address
.getUuid().isPresent()) {
204 json
.writeStringField("uuid", sessionInfo
.address
.getUuid().get().toString());
206 json
.writeNumberField("deviceId", sessionInfo
.deviceId
);
207 json
.writeStringField("record", Base64
.getEncoder().encodeToString(sessionInfo
.sessionRecord
));
208 json
.writeEndObject();
210 json
.writeEndArray();