]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/storage/protocol/JsonSessionStore.java
1b5384a4f5881387f2b006fbf4b44fcfb19660a1
[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.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;
20
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;
26
27 class JsonSessionStore implements SignalServiceSessionStore {
28
29 private final static Logger logger = LoggerFactory.getLogger(JsonSessionStore.class);
30
31 private final List<SessionInfo> sessions = new ArrayList<>();
32
33 private SignalServiceAddressResolver resolver;
34
35 public JsonSessionStore() {
36 }
37
38 public void setResolver(final SignalServiceAddressResolver resolver) {
39 this.resolver = resolver;
40 }
41
42 private SignalServiceAddress resolveSignalServiceAddress(String identifier) {
43 if (resolver != null) {
44 return resolver.resolveSignalServiceAddress(identifier);
45 } else {
46 return Utils.getSignalServiceAddressFromIdentifier(identifier);
47 }
48 }
49
50 @Override
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()) {
55 try {
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();
60 }
61 }
62 }
63
64 return new SessionRecord();
65 }
66
67 public synchronized List<SessionInfo> getSessions() {
68 return sessions;
69 }
70
71 @Override
72 public synchronized List<Integer> getSubDeviceSessions(String name) {
73 var serviceAddress = resolveSignalServiceAddress(name);
74
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);
79 }
80 }
81
82 return deviceIds;
83 }
84
85 @Override
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;
92 }
93 info.sessionRecord = record.serialize();
94 return;
95 }
96 }
97
98 sessions.add(new SessionInfo(serviceAddress, address.getDeviceId(), record.serialize()));
99 }
100
101 @Override
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;
107 try {
108 sessionRecord = new SessionRecord(info.sessionRecord);
109 } catch (IOException e) {
110 logger.warn("Failed to check session: {}", e.getMessage());
111 return false;
112 }
113
114 return sessionRecord.hasSenderChain()
115 && sessionRecord.getSessionVersion() == CiphertextMessage.CURRENT_VERSION;
116 }
117 }
118 return false;
119 }
120
121 @Override
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());
125 }
126
127 @Override
128 public synchronized void deleteAllSessions(String name) {
129 var serviceAddress = resolveSignalServiceAddress(name);
130 deleteAllSessions(serviceAddress);
131 }
132
133 public synchronized void deleteAllSessions(SignalServiceAddress serviceAddress) {
134 sessions.removeIf(info -> info.address.matches(serviceAddress));
135 }
136
137 @Override
138 public void archiveSession(final SignalProtocolAddress address) {
139 final var sessionRecord = loadSession(address);
140 if (sessionRecord == null) {
141 return;
142 }
143 sessionRecord.archiveCurrentState();
144 storeSession(address, sessionRecord);
145 }
146
147 public void archiveAllSessions() {
148 for (var info : sessions) {
149 try {
150 final var sessionRecord = new SessionRecord(info.sessionRecord);
151 sessionRecord.archiveCurrentState();
152 info.sessionRecord = sessionRecord.serialize();
153 } catch (IOException ignored) {
154 }
155 }
156 }
157
158 public static class JsonSessionStoreDeserializer extends JsonDeserializer<JsonSessionStore> {
159
160 @Override
161 public JsonSessionStore deserialize(
162 JsonParser jsonParser, DeserializationContext deserializationContext
163 ) throws IOException {
164 JsonNode node = jsonParser.getCodec().readTree(jsonParser);
165
166 var sessionStore = new JsonSessionStore();
167
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
173 continue;
174 }
175
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);
184 }
185 }
186
187 return sessionStore;
188 }
189 }
190
191 public static class JsonSessionStoreSerializer extends JsonSerializer<JsonSessionStore> {
192
193 @Override
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());
202 }
203 if (sessionInfo.address.getUuid().isPresent()) {
204 json.writeStringField("uuid", sessionInfo.address.getUuid().get().toString());
205 }
206 json.writeNumberField("deviceId", sessionInfo.deviceId);
207 json.writeStringField("record", Base64.getEncoder().encodeToString(sessionInfo.sessionRecord));
208 json.writeEndObject();
209 }
210 json.writeEndArray();
211 }
212 }
213
214 }