]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/storage/protocol/JsonSessionStore.java
94d2d681f5e4ff60ecebe1249cd9cfaa3f7c81a0
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / storage / protocol / JsonSessionStore.java
1 package org.asamk.signal.manager.storage.protocol;
2
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;
10
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.state.SessionRecord;
16 import org.whispersystems.signalservice.api.SignalServiceSessionStore;
17 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
18 import org.whispersystems.signalservice.api.util.UuidUtil;
19
20 import java.io.IOException;
21 import java.util.ArrayList;
22 import java.util.Base64;
23 import java.util.LinkedList;
24 import java.util.List;
25
26 class JsonSessionStore implements SignalServiceSessionStore {
27
28 private final static Logger logger = LoggerFactory.getLogger(JsonSessionStore.class);
29
30 private final List<SessionInfo> sessions = new ArrayList<>();
31
32 private SignalServiceAddressResolver resolver;
33
34 public JsonSessionStore() {
35 }
36
37 public void setResolver(final SignalServiceAddressResolver resolver) {
38 this.resolver = resolver;
39 }
40
41 private SignalServiceAddress resolveSignalServiceAddress(String identifier) {
42 if (resolver != null) {
43 return resolver.resolveSignalServiceAddress(identifier);
44 } else {
45 return Utils.getSignalServiceAddressFromIdentifier(identifier);
46 }
47 }
48
49 @Override
50 public synchronized SessionRecord loadSession(SignalProtocolAddress address) {
51 var serviceAddress = resolveSignalServiceAddress(address.getName());
52 for (var info : sessions) {
53 if (info.address.matches(serviceAddress) && info.deviceId == address.getDeviceId()) {
54 try {
55 return new SessionRecord(info.sessionRecord);
56 } catch (IOException e) {
57 logger.warn("Failed to load session, resetting session: {}", e.getMessage());
58 final var sessionRecord = new SessionRecord();
59 info.sessionRecord = sessionRecord.serialize();
60 return sessionRecord;
61 }
62 }
63 }
64
65 return new SessionRecord();
66 }
67
68 public synchronized List<SessionInfo> getSessions() {
69 return sessions;
70 }
71
72 @Override
73 public synchronized List<Integer> getSubDeviceSessions(String name) {
74 var serviceAddress = resolveSignalServiceAddress(name);
75
76 var deviceIds = new LinkedList<Integer>();
77 for (var info : sessions) {
78 if (info.address.matches(serviceAddress) && info.deviceId != 1) {
79 deviceIds.add(info.deviceId);
80 }
81 }
82
83 return deviceIds;
84 }
85
86 @Override
87 public synchronized void storeSession(SignalProtocolAddress address, SessionRecord record) {
88 var serviceAddress = resolveSignalServiceAddress(address.getName());
89 for (var info : sessions) {
90 if (info.address.matches(serviceAddress) && info.deviceId == address.getDeviceId()) {
91 if (!info.address.getUuid().isPresent() || !info.address.getNumber().isPresent()) {
92 info.address = serviceAddress;
93 }
94 info.sessionRecord = record.serialize();
95 return;
96 }
97 }
98
99 sessions.add(new SessionInfo(serviceAddress, address.getDeviceId(), record.serialize()));
100 }
101
102 @Override
103 public synchronized boolean containsSession(SignalProtocolAddress address) {
104 var serviceAddress = resolveSignalServiceAddress(address.getName());
105 for (var info : sessions) {
106 if (info.address.matches(serviceAddress) && info.deviceId == address.getDeviceId()) {
107 return true;
108 }
109 }
110 return false;
111 }
112
113 @Override
114 public synchronized void deleteSession(SignalProtocolAddress address) {
115 var serviceAddress = resolveSignalServiceAddress(address.getName());
116 sessions.removeIf(info -> info.address.matches(serviceAddress) && info.deviceId == address.getDeviceId());
117 }
118
119 @Override
120 public synchronized void deleteAllSessions(String name) {
121 var serviceAddress = resolveSignalServiceAddress(name);
122 deleteAllSessions(serviceAddress);
123 }
124
125 public synchronized void deleteAllSessions(SignalServiceAddress serviceAddress) {
126 sessions.removeIf(info -> info.address.matches(serviceAddress));
127 }
128
129 @Override
130 public void archiveSession(final SignalProtocolAddress address) {
131 final var sessionRecord = loadSession(address);
132 if (sessionRecord == null) {
133 return;
134 }
135 sessionRecord.archiveCurrentState();
136 storeSession(address, sessionRecord);
137 }
138
139 public void archiveAllSessions() {
140 for (var info : sessions) {
141 try {
142 final var sessionRecord = new SessionRecord(info.sessionRecord);
143 sessionRecord.archiveCurrentState();
144 info.sessionRecord = sessionRecord.serialize();
145 } catch (IOException ignored) {
146 }
147 }
148 }
149
150 public static class JsonSessionStoreDeserializer extends JsonDeserializer<JsonSessionStore> {
151
152 @Override
153 public JsonSessionStore deserialize(
154 JsonParser jsonParser, DeserializationContext deserializationContext
155 ) throws IOException {
156 JsonNode node = jsonParser.getCodec().readTree(jsonParser);
157
158 var sessionStore = new JsonSessionStore();
159
160 if (node.isArray()) {
161 for (var session : node) {
162 var sessionName = session.hasNonNull("name") ? session.get("name").asText() : null;
163 if (UuidUtil.isUuid(sessionName)) {
164 // Ignore sessions that were incorrectly created with UUIDs as name
165 continue;
166 }
167
168 var uuid = session.hasNonNull("uuid") ? UuidUtil.parseOrNull(session.get("uuid").asText()) : null;
169 final var serviceAddress = uuid == null
170 ? Utils.getSignalServiceAddressFromIdentifier(sessionName)
171 : new SignalServiceAddress(uuid, sessionName);
172 final var deviceId = session.get("deviceId").asInt();
173 final var record = Base64.getDecoder().decode(session.get("record").asText());
174 var sessionInfo = new SessionInfo(serviceAddress, deviceId, record);
175 sessionStore.sessions.add(sessionInfo);
176 }
177 }
178
179 return sessionStore;
180 }
181 }
182
183 public static class JsonSessionStoreSerializer extends JsonSerializer<JsonSessionStore> {
184
185 @Override
186 public void serialize(
187 JsonSessionStore jsonSessionStore, JsonGenerator json, SerializerProvider serializerProvider
188 ) throws IOException {
189 json.writeStartArray();
190 for (var sessionInfo : jsonSessionStore.sessions) {
191 json.writeStartObject();
192 if (sessionInfo.address.getNumber().isPresent()) {
193 json.writeStringField("name", sessionInfo.address.getNumber().get());
194 }
195 if (sessionInfo.address.getUuid().isPresent()) {
196 json.writeStringField("uuid", sessionInfo.address.getUuid().get().toString());
197 }
198 json.writeNumberField("deviceId", sessionInfo.deviceId);
199 json.writeStringField("record", Base64.getEncoder().encodeToString(sessionInfo.sessionRecord));
200 json.writeEndObject();
201 }
202 json.writeEndArray();
203 }
204 }
205
206 }