]> nmode's Git Repositories - signal-cli/blob - src/main/java/org/asamk/signal/storage/SignalAccount.java
Reformat imports
[signal-cli] / src / main / java / org / asamk / signal / storage / SignalAccount.java
1 package org.asamk.signal.storage;
2
3 import com.fasterxml.jackson.annotation.JsonAutoDetect;
4 import com.fasterxml.jackson.annotation.PropertyAccessor;
5 import com.fasterxml.jackson.core.JsonGenerator;
6 import com.fasterxml.jackson.core.JsonParser;
7 import com.fasterxml.jackson.databind.DeserializationFeature;
8 import com.fasterxml.jackson.databind.JsonNode;
9 import com.fasterxml.jackson.databind.ObjectMapper;
10 import com.fasterxml.jackson.databind.SerializationFeature;
11 import com.fasterxml.jackson.databind.node.ObjectNode;
12
13 import org.asamk.signal.storage.contacts.JsonContactsStore;
14 import org.asamk.signal.storage.groups.JsonGroupStore;
15 import org.asamk.signal.storage.protocol.JsonSignalProtocolStore;
16 import org.asamk.signal.storage.threads.JsonThreadStore;
17 import org.asamk.signal.util.IOUtils;
18 import org.asamk.signal.util.Util;
19 import org.whispersystems.libsignal.IdentityKeyPair;
20 import org.whispersystems.libsignal.state.PreKeyRecord;
21 import org.whispersystems.libsignal.state.SignedPreKeyRecord;
22 import org.whispersystems.libsignal.util.Medium;
23 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
24 import org.whispersystems.signalservice.internal.util.Base64;
25
26 import java.io.File;
27 import java.io.IOException;
28 import java.io.RandomAccessFile;
29 import java.nio.channels.Channels;
30 import java.nio.channels.FileChannel;
31 import java.nio.channels.FileLock;
32 import java.util.Collection;
33
34 public class SignalAccount {
35
36 private final ObjectMapper jsonProcessor = new ObjectMapper();
37 private FileChannel fileChannel;
38 private FileLock lock;
39 private String username;
40 private int deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
41 private boolean isMultiDevice = false;
42 private String password;
43 private String registrationLockPin;
44 private String signalingKey;
45 private byte[] profileKey;
46 private int preKeyIdOffset;
47 private int nextSignedPreKeyId;
48
49 private boolean registered = false;
50
51 private JsonSignalProtocolStore signalProtocolStore;
52 private JsonGroupStore groupStore;
53 private JsonContactsStore contactStore;
54 private JsonThreadStore threadStore;
55
56 private SignalAccount() {
57 jsonProcessor.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); // disable autodetect
58 jsonProcessor.enable(SerializationFeature.INDENT_OUTPUT); // for pretty print, you can disable it.
59 jsonProcessor.enable(SerializationFeature.WRITE_NULL_MAP_VALUES);
60 jsonProcessor.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
61 jsonProcessor.disable(JsonParser.Feature.AUTO_CLOSE_SOURCE);
62 jsonProcessor.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET);
63 }
64
65 public static SignalAccount load(String dataPath, String username) throws IOException {
66 SignalAccount account = new SignalAccount();
67 IOUtils.createPrivateDirectories(dataPath);
68 account.openFileChannel(getFileName(dataPath, username));
69 account.load();
70 return account;
71 }
72
73 public static SignalAccount create(String dataPath, String username, IdentityKeyPair identityKey, int registrationId, byte[] profileKey) throws IOException {
74 IOUtils.createPrivateDirectories(dataPath);
75
76 SignalAccount account = new SignalAccount();
77 account.openFileChannel(getFileName(dataPath, username));
78
79 account.username = username;
80 account.profileKey = profileKey;
81 account.signalProtocolStore = new JsonSignalProtocolStore(identityKey, registrationId);
82 account.groupStore = new JsonGroupStore();
83 account.threadStore = new JsonThreadStore();
84 account.contactStore = new JsonContactsStore();
85 account.registered = false;
86
87 return account;
88 }
89
90 public static SignalAccount createLinkedAccount(String dataPath, String username, String password, int deviceId, IdentityKeyPair identityKey, int registrationId, String signalingKey, byte[] profileKey) throws IOException {
91 IOUtils.createPrivateDirectories(dataPath);
92
93 SignalAccount account = new SignalAccount();
94 account.openFileChannel(getFileName(dataPath, username));
95
96 account.username = username;
97 account.password = password;
98 account.profileKey = profileKey;
99 account.deviceId = deviceId;
100 account.signalingKey = signalingKey;
101 account.signalProtocolStore = new JsonSignalProtocolStore(identityKey, registrationId);
102 account.groupStore = new JsonGroupStore();
103 account.threadStore = new JsonThreadStore();
104 account.contactStore = new JsonContactsStore();
105 account.registered = true;
106 account.isMultiDevice = true;
107
108 return account;
109 }
110
111 public static SignalAccount createTemporaryAccount(IdentityKeyPair identityKey, int registrationId) {
112 SignalAccount account = new SignalAccount();
113
114 account.signalProtocolStore = new JsonSignalProtocolStore(identityKey, registrationId);
115 account.registered = false;
116
117 return account;
118 }
119
120 public static String getFileName(String dataPath, String username) {
121 return dataPath + "/" + username;
122 }
123
124 public static boolean userExists(String dataPath, String username) {
125 if (username == null) {
126 return false;
127 }
128 File f = new File(getFileName(dataPath, username));
129 return !(!f.exists() || f.isDirectory());
130 }
131
132 private void load() throws IOException {
133 JsonNode rootNode;
134 synchronized (fileChannel) {
135 fileChannel.position(0);
136 rootNode = jsonProcessor.readTree(Channels.newInputStream(fileChannel));
137 }
138
139 JsonNode node = rootNode.get("deviceId");
140 if (node != null) {
141 deviceId = node.asInt();
142 }
143 if (rootNode.has("isMultiDevice")) {
144 isMultiDevice = Util.getNotNullNode(rootNode, "isMultiDevice").asBoolean();
145 }
146 username = Util.getNotNullNode(rootNode, "username").asText();
147 password = Util.getNotNullNode(rootNode, "password").asText();
148 JsonNode pinNode = rootNode.get("registrationLockPin");
149 registrationLockPin = pinNode == null || pinNode.isNull() ? null : pinNode.asText();
150 if (rootNode.has("signalingKey")) {
151 signalingKey = Util.getNotNullNode(rootNode, "signalingKey").asText();
152 }
153 if (rootNode.has("preKeyIdOffset")) {
154 preKeyIdOffset = Util.getNotNullNode(rootNode, "preKeyIdOffset").asInt(0);
155 } else {
156 preKeyIdOffset = 0;
157 }
158 if (rootNode.has("nextSignedPreKeyId")) {
159 nextSignedPreKeyId = Util.getNotNullNode(rootNode, "nextSignedPreKeyId").asInt();
160 } else {
161 nextSignedPreKeyId = 0;
162 }
163 if (rootNode.has("profileKey")) {
164 profileKey = Base64.decode(Util.getNotNullNode(rootNode, "profileKey").asText());
165 }
166
167 signalProtocolStore = jsonProcessor.convertValue(Util.getNotNullNode(rootNode, "axolotlStore"), JsonSignalProtocolStore.class);
168 registered = Util.getNotNullNode(rootNode, "registered").asBoolean();
169 JsonNode groupStoreNode = rootNode.get("groupStore");
170 if (groupStoreNode != null) {
171 groupStore = jsonProcessor.convertValue(groupStoreNode, JsonGroupStore.class);
172 }
173 if (groupStore == null) {
174 groupStore = new JsonGroupStore();
175 }
176
177 JsonNode contactStoreNode = rootNode.get("contactStore");
178 if (contactStoreNode != null) {
179 contactStore = jsonProcessor.convertValue(contactStoreNode, JsonContactsStore.class);
180 }
181 if (contactStore == null) {
182 contactStore = new JsonContactsStore();
183 }
184 JsonNode threadStoreNode = rootNode.get("threadStore");
185 if (threadStoreNode != null) {
186 threadStore = jsonProcessor.convertValue(threadStoreNode, JsonThreadStore.class);
187 }
188 if (threadStore == null) {
189 threadStore = new JsonThreadStore();
190 }
191 }
192
193 public void save() {
194 if (fileChannel == null) {
195 return;
196 }
197 ObjectNode rootNode = jsonProcessor.createObjectNode();
198 rootNode.put("username", username)
199 .put("deviceId", deviceId)
200 .put("isMultiDevice", isMultiDevice)
201 .put("password", password)
202 .put("registrationLockPin", registrationLockPin)
203 .put("signalingKey", signalingKey)
204 .put("preKeyIdOffset", preKeyIdOffset)
205 .put("nextSignedPreKeyId", nextSignedPreKeyId)
206 .put("profileKey", Base64.encodeBytes(profileKey))
207 .put("registered", registered)
208 .putPOJO("axolotlStore", signalProtocolStore)
209 .putPOJO("groupStore", groupStore)
210 .putPOJO("contactStore", contactStore)
211 .putPOJO("threadStore", threadStore)
212 ;
213 try {
214 synchronized (fileChannel) {
215 fileChannel.position(0);
216 jsonProcessor.writeValue(Channels.newOutputStream(fileChannel), rootNode);
217 fileChannel.truncate(fileChannel.position());
218 fileChannel.force(false);
219 }
220 } catch (Exception e) {
221 System.err.println(String.format("Error saving file: %s", e.getMessage()));
222 }
223 }
224
225 private void openFileChannel(String fileName) throws IOException {
226 if (fileChannel != null) {
227 return;
228 }
229
230 if (!new File(fileName).exists()) {
231 IOUtils.createPrivateFile(fileName);
232 }
233 fileChannel = new RandomAccessFile(new File(fileName), "rw").getChannel();
234 lock = fileChannel.tryLock();
235 if (lock == null) {
236 System.err.println("Config file is in use by another instance, waiting…");
237 lock = fileChannel.lock();
238 System.err.println("Config file lock acquired.");
239 }
240 }
241
242 public void addPreKeys(Collection<PreKeyRecord> records) {
243 for (PreKeyRecord record : records) {
244 signalProtocolStore.storePreKey(record.getId(), record);
245 }
246 preKeyIdOffset = (preKeyIdOffset + records.size()) % Medium.MAX_VALUE;
247 }
248
249 public void addSignedPreKey(SignedPreKeyRecord record) {
250 signalProtocolStore.storeSignedPreKey(record.getId(), record);
251 nextSignedPreKeyId = (nextSignedPreKeyId + 1) % Medium.MAX_VALUE;
252 }
253
254 public JsonSignalProtocolStore getSignalProtocolStore() {
255 return signalProtocolStore;
256 }
257
258 public JsonGroupStore getGroupStore() {
259 return groupStore;
260 }
261
262 public JsonContactsStore getContactStore() {
263 return contactStore;
264 }
265
266 public JsonThreadStore getThreadStore() {
267 return threadStore;
268 }
269
270 public String getUsername() {
271 return username;
272 }
273
274 public int getDeviceId() {
275 return deviceId;
276 }
277
278 public String getPassword() {
279 return password;
280 }
281
282 public void setPassword(final String password) {
283 this.password = password;
284 }
285
286 public String getRegistrationLockPin() {
287 return registrationLockPin;
288 }
289
290 public void setRegistrationLockPin(final String registrationLockPin) {
291 this.registrationLockPin = registrationLockPin;
292 }
293
294 public String getSignalingKey() {
295 return signalingKey;
296 }
297
298 public void setSignalingKey(final String signalingKey) {
299 this.signalingKey = signalingKey;
300 }
301
302 public byte[] getProfileKey() {
303 return profileKey;
304 }
305
306 public void setProfileKey(final byte[] profileKey) {
307 this.profileKey = profileKey;
308 }
309
310 public int getPreKeyIdOffset() {
311 return preKeyIdOffset;
312 }
313
314 public int getNextSignedPreKeyId() {
315 return nextSignedPreKeyId;
316 }
317
318 public boolean isRegistered() {
319 return registered;
320 }
321
322 public void setRegistered(final boolean registered) {
323 this.registered = registered;
324 }
325
326 public boolean isMultiDevice() {
327 return isMultiDevice;
328 }
329
330 public void setMultiDevice(final boolean multiDevice) {
331 isMultiDevice = multiDevice;
332 }
333 }