]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java
a6593fd96a604f69610d6ba50a904b9c98fa1686
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / storage / SignalAccount.java
1 package org.asamk.signal.manager.storage;
2
3 import com.fasterxml.jackson.databind.JsonNode;
4 import com.fasterxml.jackson.databind.ObjectMapper;
5
6 import org.asamk.signal.manager.api.Pair;
7 import org.asamk.signal.manager.api.TrustLevel;
8 import org.asamk.signal.manager.groups.GroupId;
9 import org.asamk.signal.manager.storage.configuration.ConfigurationStore;
10 import org.asamk.signal.manager.storage.contacts.ContactsStore;
11 import org.asamk.signal.manager.storage.contacts.LegacyJsonContactsStore;
12 import org.asamk.signal.manager.storage.groups.GroupInfoV1;
13 import org.asamk.signal.manager.storage.groups.GroupInfoV2;
14 import org.asamk.signal.manager.storage.groups.GroupStore;
15 import org.asamk.signal.manager.storage.identities.IdentityKeyStore;
16 import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
17 import org.asamk.signal.manager.storage.messageCache.MessageCache;
18 import org.asamk.signal.manager.storage.prekeys.PreKeyStore;
19 import org.asamk.signal.manager.storage.prekeys.SignedPreKeyStore;
20 import org.asamk.signal.manager.storage.profiles.LegacyProfileStore;
21 import org.asamk.signal.manager.storage.profiles.ProfileStore;
22 import org.asamk.signal.manager.storage.protocol.LegacyJsonSignalProtocolStore;
23 import org.asamk.signal.manager.storage.protocol.SignalProtocolStore;
24 import org.asamk.signal.manager.storage.recipients.Contact;
25 import org.asamk.signal.manager.storage.recipients.LegacyRecipientStore;
26 import org.asamk.signal.manager.storage.recipients.Profile;
27 import org.asamk.signal.manager.storage.recipients.RecipientAddress;
28 import org.asamk.signal.manager.storage.recipients.RecipientId;
29 import org.asamk.signal.manager.storage.recipients.RecipientStore;
30 import org.asamk.signal.manager.storage.sendLog.MessageSendLogStore;
31 import org.asamk.signal.manager.storage.senderKeys.SenderKeyStore;
32 import org.asamk.signal.manager.storage.sessions.SessionStore;
33 import org.asamk.signal.manager.storage.stickers.StickerStore;
34 import org.asamk.signal.manager.storage.threads.LegacyJsonThreadStore;
35 import org.asamk.signal.manager.util.IOUtils;
36 import org.asamk.signal.manager.util.KeyUtils;
37 import org.signal.zkgroup.InvalidInputException;
38 import org.signal.zkgroup.profiles.ProfileKey;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41 import org.whispersystems.libsignal.IdentityKeyPair;
42 import org.whispersystems.libsignal.SignalProtocolAddress;
43 import org.whispersystems.libsignal.state.PreKeyRecord;
44 import org.whispersystems.libsignal.state.SessionRecord;
45 import org.whispersystems.libsignal.state.SignedPreKeyRecord;
46 import org.whispersystems.libsignal.util.Medium;
47 import org.whispersystems.signalservice.api.SignalServiceAccountDataStore;
48 import org.whispersystems.signalservice.api.SignalServiceDataStore;
49 import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
50 import org.whispersystems.signalservice.api.kbs.MasterKey;
51 import org.whispersystems.signalservice.api.push.ACI;
52 import org.whispersystems.signalservice.api.push.DistributionId;
53 import org.whispersystems.signalservice.api.push.PNI;
54 import org.whispersystems.signalservice.api.push.ServiceId;
55 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
56 import org.whispersystems.signalservice.api.storage.StorageKey;
57 import org.whispersystems.signalservice.api.util.CredentialsProvider;
58 import org.whispersystems.signalservice.api.util.UuidUtil;
59
60 import java.io.ByteArrayInputStream;
61 import java.io.ByteArrayOutputStream;
62 import java.io.Closeable;
63 import java.io.File;
64 import java.io.IOException;
65 import java.io.RandomAccessFile;
66 import java.nio.channels.Channels;
67 import java.nio.channels.ClosedChannelException;
68 import java.nio.channels.FileChannel;
69 import java.nio.channels.FileLock;
70 import java.security.SecureRandom;
71 import java.sql.SQLException;
72 import java.util.Base64;
73 import java.util.Date;
74 import java.util.HashSet;
75 import java.util.List;
76 import java.util.function.Supplier;
77
78 public class SignalAccount implements Closeable {
79
80 private final static Logger logger = LoggerFactory.getLogger(SignalAccount.class);
81
82 private static final int MINIMUM_STORAGE_VERSION = 1;
83 private static final int CURRENT_STORAGE_VERSION = 3;
84
85 private final Object LOCK = new Object();
86
87 private final ObjectMapper jsonProcessor = Utils.createStorageObjectMapper();
88
89 private final FileChannel fileChannel;
90 private final FileLock lock;
91
92 private int previousStorageVersion;
93
94 private File dataPath;
95 private String accountPath;
96 private String number;
97 private ACI aci;
98 private PNI pni;
99 private String encryptedDeviceName;
100 private int deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
101 private boolean isMultiDevice = false;
102 private String password;
103 private String registrationLockPin;
104 private MasterKey pinMasterKey;
105 private StorageKey storageKey;
106 private long storageManifestVersion = -1;
107 private ProfileKey profileKey;
108 private int preKeyIdOffset = 1;
109 private int nextSignedPreKeyId = 1;
110 private IdentityKeyPair aciIdentityKeyPair;
111 private IdentityKeyPair pniIdentityKeyPair;
112 private int localRegistrationId;
113 private TrustNewIdentity trustNewIdentity;
114 private long lastReceiveTimestamp = 0;
115
116 private boolean registered = false;
117
118 private SignalProtocolStore signalProtocolStore;
119 private PreKeyStore preKeyStore;
120 private SignedPreKeyStore signedPreKeyStore;
121 private SessionStore sessionStore;
122 private IdentityKeyStore identityKeyStore;
123 private SenderKeyStore senderKeyStore;
124 private GroupStore groupStore;
125 private GroupStore.Storage groupStoreStorage;
126 private RecipientStore recipientStore;
127 private StickerStore stickerStore;
128 private StickerStore.Storage stickerStoreStorage;
129 private ConfigurationStore configurationStore;
130 private ConfigurationStore.Storage configurationStoreStorage;
131
132 private MessageCache messageCache;
133 private MessageSendLogStore messageSendLogStore;
134
135 private AccountDatabase accountDatabase;
136
137 private SignalAccount(final FileChannel fileChannel, final FileLock lock) {
138 this.fileChannel = fileChannel;
139 this.lock = lock;
140 }
141
142 public static SignalAccount load(
143 File dataPath, String accountPath, boolean waitForLock, final TrustNewIdentity trustNewIdentity
144 ) throws IOException {
145 logger.trace("Opening account file");
146 final var fileName = getFileName(dataPath, accountPath);
147 final var pair = openFileChannel(fileName, waitForLock);
148 try {
149 var signalAccount = new SignalAccount(pair.first(), pair.second());
150 logger.trace("Loading account file");
151 signalAccount.load(dataPath, accountPath, trustNewIdentity);
152 logger.trace("Migrating legacy parts of account file");
153 signalAccount.migrateLegacyConfigs();
154
155 return signalAccount;
156 } catch (Throwable e) {
157 pair.second().close();
158 pair.first().close();
159 throw e;
160 }
161 }
162
163 public static SignalAccount create(
164 File dataPath,
165 String accountPath,
166 String number,
167 IdentityKeyPair aciIdentityKey,
168 IdentityKeyPair pniIdentityKey,
169 int registrationId,
170 ProfileKey profileKey,
171 final TrustNewIdentity trustNewIdentity
172 ) throws IOException {
173 IOUtils.createPrivateDirectories(dataPath);
174 var fileName = getFileName(dataPath, accountPath);
175 if (!fileName.exists()) {
176 IOUtils.createPrivateFile(fileName);
177 }
178
179 final var pair = openFileChannel(fileName, true);
180 var signalAccount = new SignalAccount(pair.first(), pair.second());
181
182 signalAccount.accountPath = accountPath;
183 signalAccount.number = number;
184 signalAccount.profileKey = profileKey;
185
186 signalAccount.dataPath = dataPath;
187 signalAccount.aciIdentityKeyPair = aciIdentityKey;
188 signalAccount.pniIdentityKeyPair = pniIdentityKey;
189 signalAccount.localRegistrationId = registrationId;
190 signalAccount.trustNewIdentity = trustNewIdentity;
191 signalAccount.groupStore = new GroupStore(getGroupCachePath(dataPath, accountPath),
192 signalAccount.getRecipientStore(),
193 signalAccount::saveGroupStore);
194 signalAccount.stickerStore = new StickerStore(signalAccount::saveStickerStore);
195 signalAccount.configurationStore = new ConfigurationStore(signalAccount::saveConfigurationStore);
196
197 signalAccount.registered = false;
198
199 signalAccount.previousStorageVersion = CURRENT_STORAGE_VERSION;
200 signalAccount.migrateLegacyConfigs();
201 signalAccount.save();
202
203 return signalAccount;
204 }
205
206 public static SignalAccount createOrUpdateLinkedAccount(
207 File dataPath,
208 String accountPath,
209 String number,
210 ACI aci,
211 PNI pni,
212 String password,
213 String encryptedDeviceName,
214 int deviceId,
215 IdentityKeyPair aciIdentityKey,
216 IdentityKeyPair pniIdentityKey,
217 int registrationId,
218 ProfileKey profileKey,
219 final TrustNewIdentity trustNewIdentity
220 ) throws IOException {
221 IOUtils.createPrivateDirectories(dataPath);
222 var fileName = getFileName(dataPath, accountPath);
223 if (!fileName.exists()) {
224 return createLinkedAccount(dataPath,
225 accountPath,
226 number,
227 aci,
228 pni,
229 password,
230 encryptedDeviceName,
231 deviceId,
232 aciIdentityKey,
233 pniIdentityKey,
234 registrationId,
235 profileKey,
236 trustNewIdentity);
237 }
238
239 final var signalAccount = load(dataPath, accountPath, true, trustNewIdentity);
240 signalAccount.setProvisioningData(number,
241 aci,
242 pni,
243 password,
244 encryptedDeviceName,
245 deviceId,
246 aciIdentityKey,
247 pniIdentityKey,
248 profileKey);
249 signalAccount.getRecipientStore().resolveSelfRecipientTrusted(signalAccount.getSelfRecipientAddress());
250 signalAccount.getSessionStore().archiveAllSessions();
251 signalAccount.getSenderKeyStore().deleteAll();
252 signalAccount.clearAllPreKeys();
253 return signalAccount;
254 }
255
256 public void initDatabase() {
257 getAccountDatabase();
258 }
259
260 private void clearAllPreKeys() {
261 this.preKeyIdOffset = new SecureRandom().nextInt(Medium.MAX_VALUE);
262 this.nextSignedPreKeyId = new SecureRandom().nextInt(Medium.MAX_VALUE);
263 this.getPreKeyStore().removeAllPreKeys();
264 this.getSignedPreKeyStore().removeAllSignedPreKeys();
265 save();
266 }
267
268 private static SignalAccount createLinkedAccount(
269 File dataPath,
270 String accountPath,
271 String number,
272 ACI aci,
273 PNI pni,
274 String password,
275 String encryptedDeviceName,
276 int deviceId,
277 IdentityKeyPair aciIdentityKey,
278 IdentityKeyPair pniIdentityKey,
279 int registrationId,
280 ProfileKey profileKey,
281 final TrustNewIdentity trustNewIdentity
282 ) throws IOException {
283 var fileName = getFileName(dataPath, accountPath);
284 IOUtils.createPrivateFile(fileName);
285
286 final var pair = openFileChannel(fileName, true);
287 var signalAccount = new SignalAccount(pair.first(), pair.second());
288
289 signalAccount.setProvisioningData(number,
290 aci,
291 pni,
292 password,
293 encryptedDeviceName,
294 deviceId,
295 aciIdentityKey,
296 pniIdentityKey,
297 profileKey);
298
299 signalAccount.dataPath = dataPath;
300 signalAccount.accountPath = accountPath;
301 signalAccount.localRegistrationId = registrationId;
302 signalAccount.trustNewIdentity = trustNewIdentity;
303 signalAccount.groupStore = new GroupStore(getGroupCachePath(dataPath, accountPath),
304 signalAccount.getRecipientStore(),
305 signalAccount::saveGroupStore);
306 signalAccount.stickerStore = new StickerStore(signalAccount::saveStickerStore);
307 signalAccount.configurationStore = new ConfigurationStore(signalAccount::saveConfigurationStore);
308
309 signalAccount.getRecipientStore().resolveSelfRecipientTrusted(signalAccount.getSelfRecipientAddress());
310 signalAccount.previousStorageVersion = CURRENT_STORAGE_VERSION;
311 signalAccount.migrateLegacyConfigs();
312 signalAccount.clearAllPreKeys();
313 signalAccount.save();
314
315 return signalAccount;
316 }
317
318 private void setProvisioningData(
319 final String number,
320 final ACI aci,
321 final PNI pni,
322 final String password,
323 final String encryptedDeviceName,
324 final int deviceId,
325 final IdentityKeyPair aciIdentity,
326 final IdentityKeyPair pniIdentity,
327 final ProfileKey profileKey
328 ) {
329 this.number = number;
330 this.aci = aci;
331 this.pni = pni;
332 this.password = password;
333 this.profileKey = profileKey;
334 this.encryptedDeviceName = encryptedDeviceName;
335 this.deviceId = deviceId;
336 this.aciIdentityKeyPair = aciIdentity;
337 this.pniIdentityKeyPair = pniIdentity;
338 this.registered = true;
339 this.isMultiDevice = true;
340 this.lastReceiveTimestamp = 0;
341 this.pinMasterKey = null;
342 this.storageManifestVersion = -1;
343 this.storageKey = null;
344 }
345
346 private void migrateLegacyConfigs() {
347 if (getPassword() == null) {
348 setPassword(KeyUtils.createPassword());
349 }
350
351 if (getProfileKey() == null) {
352 // Old config file, creating new profile key
353 setProfileKey(KeyUtils.createProfileKey());
354 }
355 // Ensure our profile key is stored in profile store
356 getProfileStore().storeProfileKey(getSelfRecipientId(), getProfileKey());
357 if (previousStorageVersion < 3) {
358 for (final var group : groupStore.getGroups()) {
359 if (group instanceof GroupInfoV2 && group.getDistributionId() == null) {
360 ((GroupInfoV2) group).setDistributionId(DistributionId.create());
361 groupStore.updateGroup(group);
362 }
363 }
364 save();
365 }
366 if (isMasterDevice() && getPniIdentityKeyPair() == null) {
367 setPniIdentityKeyPair(KeyUtils.generateIdentityKeyPair());
368 }
369 }
370
371 private void mergeRecipients(RecipientId recipientId, RecipientId toBeMergedRecipientId) {
372 getSessionStore().mergeRecipients(recipientId, toBeMergedRecipientId);
373 getIdentityKeyStore().mergeRecipients(recipientId, toBeMergedRecipientId);
374 getMessageCache().mergeRecipients(recipientId, toBeMergedRecipientId);
375 getGroupStore().mergeRecipients(recipientId, toBeMergedRecipientId);
376 getSenderKeyStore().mergeRecipients(recipientId, toBeMergedRecipientId);
377 }
378
379 public void removeRecipient(final RecipientId recipientId) {
380 getSessionStore().deleteAllSessions(recipientId);
381 getIdentityKeyStore().deleteIdentity(recipientId);
382 getMessageCache().deleteMessages(recipientId);
383 getSenderKeyStore().deleteAll(recipientId);
384 getRecipientStore().deleteRecipientData(recipientId);
385 }
386
387 public static File getFileName(File dataPath, String account) {
388 return new File(dataPath, account);
389 }
390
391 private static File getUserPath(final File dataPath, final String account) {
392 final var path = new File(dataPath, account + ".d");
393 try {
394 IOUtils.createPrivateDirectories(path);
395 } catch (IOException e) {
396 throw new AssertionError("Failed to create user path", e);
397 }
398 return path;
399 }
400
401 private static File getMessageCachePath(File dataPath, String account) {
402 return new File(getUserPath(dataPath, account), "msg-cache");
403 }
404
405 private static File getGroupCachePath(File dataPath, String account) {
406 return new File(getUserPath(dataPath, account), "group-cache");
407 }
408
409 private static File getPreKeysPath(File dataPath, String account) {
410 return new File(getUserPath(dataPath, account), "pre-keys");
411 }
412
413 private static File getSignedPreKeysPath(File dataPath, String account) {
414 return new File(getUserPath(dataPath, account), "signed-pre-keys");
415 }
416
417 private static File getIdentitiesPath(File dataPath, String account) {
418 return new File(getUserPath(dataPath, account), "identities");
419 }
420
421 private static File getSessionsPath(File dataPath, String account) {
422 return new File(getUserPath(dataPath, account), "sessions");
423 }
424
425 private static File getSenderKeysPath(File dataPath, String account) {
426 return new File(getUserPath(dataPath, account), "sender-keys");
427 }
428
429 private static File getSharedSenderKeysFile(File dataPath, String account) {
430 return new File(getUserPath(dataPath, account), "shared-sender-keys-store");
431 }
432
433 private static File getRecipientsStoreFile(File dataPath, String account) {
434 return new File(getUserPath(dataPath, account), "recipients-store");
435 }
436
437 private static File getDatabaseFile(File dataPath, String account) {
438 return new File(getUserPath(dataPath, account), "account.db");
439 }
440
441 public static boolean accountFileExists(File dataPath, String account) {
442 if (account == null) {
443 return false;
444 }
445 var f = getFileName(dataPath, account);
446 return !(!f.exists() || f.isDirectory());
447 }
448
449 private void load(
450 File dataPath, String accountPath, final TrustNewIdentity trustNewIdentity
451 ) throws IOException {
452 this.dataPath = dataPath;
453 this.accountPath = accountPath;
454 final JsonNode rootNode;
455 synchronized (fileChannel) {
456 fileChannel.position(0);
457 rootNode = jsonProcessor.readTree(Channels.newInputStream(fileChannel));
458 }
459
460 if (rootNode.hasNonNull("version")) {
461 var accountVersion = rootNode.get("version").asInt(1);
462 if (accountVersion > CURRENT_STORAGE_VERSION) {
463 throw new IOException("Config file was created by a more recent version!");
464 } else if (accountVersion < MINIMUM_STORAGE_VERSION) {
465 throw new IOException("Config file was created by a no longer supported older version!");
466 }
467 previousStorageVersion = accountVersion;
468 }
469
470 number = Utils.getNotNullNode(rootNode, "username").asText();
471 if (rootNode.hasNonNull("password")) {
472 password = rootNode.get("password").asText();
473 }
474 registered = Utils.getNotNullNode(rootNode, "registered").asBoolean();
475 if (rootNode.hasNonNull("uuid")) {
476 try {
477 aci = ACI.parseOrThrow(rootNode.get("uuid").asText());
478 } catch (IllegalArgumentException e) {
479 throw new IOException("Config file contains an invalid aci/uuid, needs to be a valid UUID", e);
480 }
481 }
482 if (rootNode.hasNonNull("pni")) {
483 try {
484 pni = PNI.parseOrThrow(rootNode.get("pni").asText());
485 } catch (IllegalArgumentException e) {
486 throw new IOException("Config file contains an invalid pni, needs to be a valid UUID", e);
487 }
488 }
489 if (rootNode.hasNonNull("deviceName")) {
490 encryptedDeviceName = rootNode.get("deviceName").asText();
491 }
492 if (rootNode.hasNonNull("deviceId")) {
493 deviceId = rootNode.get("deviceId").asInt();
494 }
495 if (rootNode.hasNonNull("isMultiDevice")) {
496 isMultiDevice = rootNode.get("isMultiDevice").asBoolean();
497 }
498 if (rootNode.hasNonNull("lastReceiveTimestamp")) {
499 lastReceiveTimestamp = rootNode.get("lastReceiveTimestamp").asLong();
500 }
501 int registrationId = 0;
502 if (rootNode.hasNonNull("registrationId")) {
503 registrationId = rootNode.get("registrationId").asInt();
504 }
505 IdentityKeyPair aciIdentityKeyPair = null;
506 if (rootNode.hasNonNull("identityPrivateKey") && rootNode.hasNonNull("identityKey")) {
507 final var publicKeyBytes = Base64.getDecoder().decode(rootNode.get("identityKey").asText());
508 final var privateKeyBytes = Base64.getDecoder().decode(rootNode.get("identityPrivateKey").asText());
509 aciIdentityKeyPair = KeyUtils.getIdentityKeyPair(publicKeyBytes, privateKeyBytes);
510 }
511 if (rootNode.hasNonNull("pniIdentityPrivateKey") && rootNode.hasNonNull("pniIdentityKey")) {
512 final var publicKeyBytes = Base64.getDecoder().decode(rootNode.get("pniIdentityKey").asText());
513 final var privateKeyBytes = Base64.getDecoder().decode(rootNode.get("pniIdentityPrivateKey").asText());
514 pniIdentityKeyPair = KeyUtils.getIdentityKeyPair(publicKeyBytes, privateKeyBytes);
515 }
516
517 if (rootNode.hasNonNull("registrationLockPin")) {
518 registrationLockPin = rootNode.get("registrationLockPin").asText();
519 }
520 if (rootNode.hasNonNull("pinMasterKey")) {
521 pinMasterKey = new MasterKey(Base64.getDecoder().decode(rootNode.get("pinMasterKey").asText()));
522 }
523 if (rootNode.hasNonNull("storageKey")) {
524 storageKey = new StorageKey(Base64.getDecoder().decode(rootNode.get("storageKey").asText()));
525 }
526 if (rootNode.hasNonNull("storageManifestVersion")) {
527 storageManifestVersion = rootNode.get("storageManifestVersion").asLong();
528 }
529 if (rootNode.hasNonNull("preKeyIdOffset")) {
530 preKeyIdOffset = rootNode.get("preKeyIdOffset").asInt(1);
531 } else {
532 preKeyIdOffset = 1;
533 }
534 if (rootNode.hasNonNull("nextSignedPreKeyId")) {
535 nextSignedPreKeyId = rootNode.get("nextSignedPreKeyId").asInt(1);
536 } else {
537 nextSignedPreKeyId = 1;
538 }
539 if (rootNode.hasNonNull("profileKey")) {
540 try {
541 profileKey = new ProfileKey(Base64.getDecoder().decode(rootNode.get("profileKey").asText()));
542 } catch (InvalidInputException e) {
543 throw new IOException(
544 "Config file contains an invalid profileKey, needs to be base64 encoded array of 32 bytes",
545 e);
546 }
547 }
548
549 var migratedLegacyConfig = false;
550 final var legacySignalProtocolStore = rootNode.hasNonNull("axolotlStore")
551 ? jsonProcessor.convertValue(Utils.getNotNullNode(rootNode, "axolotlStore"),
552 LegacyJsonSignalProtocolStore.class)
553 : null;
554 if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacyIdentityKeyStore() != null) {
555 aciIdentityKeyPair = legacySignalProtocolStore.getLegacyIdentityKeyStore().getIdentityKeyPair();
556 registrationId = legacySignalProtocolStore.getLegacyIdentityKeyStore().getLocalRegistrationId();
557 migratedLegacyConfig = true;
558 }
559
560 this.aciIdentityKeyPair = aciIdentityKeyPair;
561 this.localRegistrationId = registrationId;
562 this.trustNewIdentity = trustNewIdentity;
563
564 migratedLegacyConfig = loadLegacyStores(rootNode, legacySignalProtocolStore) || migratedLegacyConfig;
565
566 if (rootNode.hasNonNull("groupStore")) {
567 groupStoreStorage = jsonProcessor.convertValue(rootNode.get("groupStore"), GroupStore.Storage.class);
568 groupStore = GroupStore.fromStorage(groupStoreStorage,
569 getGroupCachePath(dataPath, accountPath),
570 getRecipientStore(),
571 this::saveGroupStore);
572 } else {
573 groupStore = new GroupStore(getGroupCachePath(dataPath, accountPath),
574 getRecipientStore(),
575 this::saveGroupStore);
576 }
577
578 if (rootNode.hasNonNull("stickerStore")) {
579 stickerStoreStorage = jsonProcessor.convertValue(rootNode.get("stickerStore"), StickerStore.Storage.class);
580 stickerStore = StickerStore.fromStorage(stickerStoreStorage, this::saveStickerStore);
581 } else {
582 stickerStore = new StickerStore(this::saveStickerStore);
583 }
584
585 if (rootNode.hasNonNull("configurationStore")) {
586 configurationStoreStorage = jsonProcessor.convertValue(rootNode.get("configurationStore"),
587 ConfigurationStore.Storage.class);
588 configurationStore = ConfigurationStore.fromStorage(configurationStoreStorage,
589 this::saveConfigurationStore);
590 } else {
591 configurationStore = new ConfigurationStore(this::saveConfigurationStore);
592 }
593
594 migratedLegacyConfig = loadLegacyThreadStore(rootNode) || migratedLegacyConfig;
595
596 if (migratedLegacyConfig) {
597 save();
598 }
599 }
600
601 private boolean loadLegacyStores(
602 final JsonNode rootNode, final LegacyJsonSignalProtocolStore legacySignalProtocolStore
603 ) {
604 var migrated = false;
605 var legacyRecipientStoreNode = rootNode.get("recipientStore");
606 if (legacyRecipientStoreNode != null) {
607 logger.debug("Migrating legacy recipient store.");
608 var legacyRecipientStore = jsonProcessor.convertValue(legacyRecipientStoreNode, LegacyRecipientStore.class);
609 if (legacyRecipientStore != null) {
610 getRecipientStore().resolveRecipientsTrusted(legacyRecipientStore.getAddresses());
611 }
612 getRecipientStore().resolveSelfRecipientTrusted(getSelfRecipientAddress());
613 migrated = true;
614 }
615
616 if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacyPreKeyStore() != null) {
617 logger.debug("Migrating legacy pre key store.");
618 for (var entry : legacySignalProtocolStore.getLegacyPreKeyStore().getPreKeys().entrySet()) {
619 try {
620 getPreKeyStore().storePreKey(entry.getKey(), new PreKeyRecord(entry.getValue()));
621 } catch (IOException e) {
622 logger.warn("Failed to migrate pre key, ignoring", e);
623 }
624 }
625 migrated = true;
626 }
627
628 if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacySignedPreKeyStore() != null) {
629 logger.debug("Migrating legacy signed pre key store.");
630 for (var entry : legacySignalProtocolStore.getLegacySignedPreKeyStore().getSignedPreKeys().entrySet()) {
631 try {
632 getSignedPreKeyStore().storeSignedPreKey(entry.getKey(), new SignedPreKeyRecord(entry.getValue()));
633 } catch (IOException e) {
634 logger.warn("Failed to migrate signed pre key, ignoring", e);
635 }
636 }
637 migrated = true;
638 }
639
640 if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacySessionStore() != null) {
641 logger.debug("Migrating legacy session store.");
642 for (var session : legacySignalProtocolStore.getLegacySessionStore().getSessions()) {
643 try {
644 getSessionStore().storeSession(new SignalProtocolAddress(session.address.getIdentifier(),
645 session.deviceId), new SessionRecord(session.sessionRecord));
646 } catch (Exception e) {
647 logger.warn("Failed to migrate session, ignoring", e);
648 }
649 }
650 migrated = true;
651 }
652
653 if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacyIdentityKeyStore() != null) {
654 logger.debug("Migrating legacy identity session store.");
655 for (var identity : legacySignalProtocolStore.getLegacyIdentityKeyStore().getIdentities()) {
656 RecipientId recipientId = getRecipientStore().resolveRecipientTrusted(identity.getAddress());
657 getIdentityKeyStore().saveIdentity(recipientId, identity.getIdentityKey(), identity.getDateAdded());
658 getIdentityKeyStore().setIdentityTrustLevel(recipientId,
659 identity.getIdentityKey(),
660 identity.getTrustLevel());
661 }
662 migrated = true;
663 }
664
665 if (rootNode.hasNonNull("contactStore")) {
666 logger.debug("Migrating legacy contact store.");
667 final var contactStoreNode = rootNode.get("contactStore");
668 final var contactStore = jsonProcessor.convertValue(contactStoreNode, LegacyJsonContactsStore.class);
669 for (var contact : contactStore.getContacts()) {
670 final var recipientId = getRecipientStore().resolveRecipientTrusted(contact.getAddress());
671 getRecipientStore().storeContact(recipientId,
672 new Contact(contact.name,
673 contact.color,
674 contact.messageExpirationTime,
675 contact.blocked,
676 contact.archived));
677
678 // Store profile keys only in profile store
679 var profileKeyString = contact.profileKey;
680 if (profileKeyString != null) {
681 final ProfileKey profileKey;
682 try {
683 profileKey = new ProfileKey(Base64.getDecoder().decode(profileKeyString));
684 getProfileStore().storeProfileKey(recipientId, profileKey);
685 } catch (InvalidInputException e) {
686 logger.warn("Failed to parse legacy contact profile key: {}", e.getMessage());
687 }
688 }
689 }
690 migrated = true;
691 }
692
693 if (rootNode.hasNonNull("profileStore")) {
694 logger.debug("Migrating legacy profile store.");
695 var profileStoreNode = rootNode.get("profileStore");
696 final var legacyProfileStore = jsonProcessor.convertValue(profileStoreNode, LegacyProfileStore.class);
697 for (var profileEntry : legacyProfileStore.getProfileEntries()) {
698 var recipientId = getRecipientStore().resolveRecipient(profileEntry.getAddress());
699 getRecipientStore().storeProfileKeyCredential(recipientId, profileEntry.getProfileKeyCredential());
700 getRecipientStore().storeProfileKey(recipientId, profileEntry.getProfileKey());
701 final var profile = profileEntry.getProfile();
702 if (profile != null) {
703 final var capabilities = new HashSet<Profile.Capability>();
704 if (profile.getCapabilities() != null) {
705 if (profile.getCapabilities().gv1Migration) {
706 capabilities.add(Profile.Capability.gv1Migration);
707 }
708 if (profile.getCapabilities().storage) {
709 capabilities.add(Profile.Capability.storage);
710 }
711 }
712 final var newProfile = new Profile(profileEntry.getLastUpdateTimestamp(),
713 profile.getGivenName(),
714 profile.getFamilyName(),
715 profile.getAbout(),
716 profile.getAboutEmoji(),
717 null,
718 profile.isUnrestrictedUnidentifiedAccess()
719 ? Profile.UnidentifiedAccessMode.UNRESTRICTED
720 : profile.getUnidentifiedAccess() != null
721 ? Profile.UnidentifiedAccessMode.ENABLED
722 : Profile.UnidentifiedAccessMode.DISABLED,
723 capabilities);
724 getRecipientStore().storeProfile(recipientId, newProfile);
725 }
726 }
727 }
728
729 return migrated;
730 }
731
732 private boolean loadLegacyThreadStore(final JsonNode rootNode) {
733 var threadStoreNode = rootNode.get("threadStore");
734 if (threadStoreNode != null && !threadStoreNode.isNull()) {
735 var threadStore = jsonProcessor.convertValue(threadStoreNode, LegacyJsonThreadStore.class);
736 // Migrate thread info to group and contact store
737 for (var thread : threadStore.getThreads()) {
738 if (thread.id == null || thread.id.isEmpty()) {
739 continue;
740 }
741 try {
742 if (UuidUtil.isUuid(thread.id) || thread.id.startsWith("+")) {
743 final var recipientId = getRecipientStore().resolveRecipient(thread.id);
744 var contact = getRecipientStore().getContact(recipientId);
745 if (contact != null) {
746 getRecipientStore().storeContact(recipientId,
747 Contact.newBuilder(contact)
748 .withMessageExpirationTime(thread.messageExpirationTime)
749 .build());
750 }
751 } else {
752 var groupInfo = groupStore.getGroup(GroupId.fromBase64(thread.id));
753 if (groupInfo instanceof GroupInfoV1) {
754 ((GroupInfoV1) groupInfo).messageExpirationTime = thread.messageExpirationTime;
755 groupStore.updateGroup(groupInfo);
756 }
757 }
758 } catch (Exception e) {
759 logger.warn("Failed to read legacy thread info: {}", e.getMessage());
760 }
761 }
762 return true;
763 }
764
765 return false;
766 }
767
768 private void saveStickerStore(StickerStore.Storage storage) {
769 this.stickerStoreStorage = storage;
770 save();
771 }
772
773 private void saveGroupStore(GroupStore.Storage storage) {
774 this.groupStoreStorage = storage;
775 save();
776 }
777
778 private void saveConfigurationStore(ConfigurationStore.Storage storage) {
779 this.configurationStoreStorage = storage;
780 save();
781 }
782
783 private void save() {
784 synchronized (fileChannel) {
785 var rootNode = jsonProcessor.createObjectNode();
786 rootNode.put("version", CURRENT_STORAGE_VERSION)
787 .put("username", number)
788 .put("uuid", aci == null ? null : aci.toString())
789 .put("pni", pni == null ? null : pni.toString())
790 .put("deviceName", encryptedDeviceName)
791 .put("deviceId", deviceId)
792 .put("isMultiDevice", isMultiDevice)
793 .put("lastReceiveTimestamp", lastReceiveTimestamp)
794 .put("password", password)
795 .put("registrationId", localRegistrationId)
796 .put("identityPrivateKey",
797 Base64.getEncoder().encodeToString(aciIdentityKeyPair.getPrivateKey().serialize()))
798 .put("identityKey",
799 Base64.getEncoder().encodeToString(aciIdentityKeyPair.getPublicKey().serialize()))
800 .put("pniIdentityPrivateKey",
801 pniIdentityKeyPair == null
802 ? null
803 : Base64.getEncoder()
804 .encodeToString(pniIdentityKeyPair.getPrivateKey().serialize()))
805 .put("pniIdentityKey",
806 pniIdentityKeyPair == null
807 ? null
808 : Base64.getEncoder().encodeToString(pniIdentityKeyPair.getPublicKey().serialize()))
809 .put("registrationLockPin", registrationLockPin)
810 .put("pinMasterKey",
811 pinMasterKey == null ? null : Base64.getEncoder().encodeToString(pinMasterKey.serialize()))
812 .put("storageKey",
813 storageKey == null ? null : Base64.getEncoder().encodeToString(storageKey.serialize()))
814 .put("storageManifestVersion", storageManifestVersion == -1 ? null : storageManifestVersion)
815 .put("preKeyIdOffset", preKeyIdOffset)
816 .put("nextSignedPreKeyId", nextSignedPreKeyId)
817 .put("profileKey",
818 profileKey == null ? null : Base64.getEncoder().encodeToString(profileKey.serialize()))
819 .put("registered", registered)
820 .putPOJO("groupStore", groupStoreStorage)
821 .putPOJO("stickerStore", stickerStoreStorage)
822 .putPOJO("configurationStore", configurationStoreStorage);
823 try {
824 try (var output = new ByteArrayOutputStream()) {
825 // Write to memory first to prevent corrupting the file in case of serialization errors
826 jsonProcessor.writeValue(output, rootNode);
827 var input = new ByteArrayInputStream(output.toByteArray());
828 fileChannel.position(0);
829 input.transferTo(Channels.newOutputStream(fileChannel));
830 fileChannel.truncate(fileChannel.position());
831 fileChannel.force(false);
832 }
833 } catch (Exception e) {
834 logger.error("Error saving file: {}", e.getMessage(), e);
835 }
836 }
837 }
838
839 private static Pair<FileChannel, FileLock> openFileChannel(File fileName, boolean waitForLock) throws IOException {
840 var fileChannel = new RandomAccessFile(fileName, "rw").getChannel();
841 var lock = fileChannel.tryLock();
842 if (lock == null) {
843 if (!waitForLock) {
844 logger.debug("Config file is in use by another instance.");
845 throw new IOException("Config file is in use by another instance.");
846 }
847 logger.info("Config file is in use by another instance, waiting…");
848 lock = fileChannel.lock();
849 logger.info("Config file lock acquired.");
850 }
851 return new Pair<>(fileChannel, lock);
852 }
853
854 public void addPreKeys(List<PreKeyRecord> records) {
855 for (var record : records) {
856 if (preKeyIdOffset != record.getId()) {
857 logger.error("Invalid pre key id {}, expected {}", record.getId(), preKeyIdOffset);
858 throw new AssertionError("Invalid pre key id");
859 }
860 getPreKeyStore().storePreKey(record.getId(), record);
861 preKeyIdOffset = (preKeyIdOffset + 1) % Medium.MAX_VALUE;
862 }
863 save();
864 }
865
866 public void addSignedPreKey(SignedPreKeyRecord record) {
867 if (nextSignedPreKeyId != record.getId()) {
868 logger.error("Invalid signed pre key id {}, expected {}", record.getId(), nextSignedPreKeyId);
869 throw new AssertionError("Invalid signed pre key id");
870 }
871 getSignedPreKeyStore().storeSignedPreKey(record.getId(), record);
872 nextSignedPreKeyId = (nextSignedPreKeyId + 1) % Medium.MAX_VALUE;
873 save();
874 }
875
876 public SignalServiceDataStore getSignalServiceDataStore() {
877 return new SignalServiceDataStore() {
878 @Override
879 public SignalServiceAccountDataStore get(final ServiceId accountIdentifier) {
880 if (accountIdentifier.equals(aci)) {
881 return getSignalServiceAccountDataStore();
882 } else if (accountIdentifier.equals(pni)) {
883 throw new AssertionError("PNI not to be used yet!");
884 } else {
885 throw new IllegalArgumentException("No matching store found for " + accountIdentifier);
886 }
887 }
888
889 @Override
890 public SignalServiceAccountDataStore aci() {
891 return getSignalServiceAccountDataStore();
892 }
893
894 @Override
895 public SignalServiceAccountDataStore pni() {
896 throw new AssertionError("PNI not to be used yet!");
897 }
898
899 @Override
900 public boolean isMultiDevice() {
901 return SignalAccount.this.isMultiDevice();
902 }
903 };
904 }
905
906 public SignalServiceAccountDataStore getSignalServiceAccountDataStore() {
907 return getOrCreate(() -> signalProtocolStore,
908 () -> signalProtocolStore = new SignalProtocolStore(getPreKeyStore(),
909 getSignedPreKeyStore(),
910 getSessionStore(),
911 getIdentityKeyStore(),
912 getSenderKeyStore(),
913 this::isMultiDevice));
914 }
915
916 private PreKeyStore getPreKeyStore() {
917 return getOrCreate(() -> preKeyStore,
918 () -> preKeyStore = new PreKeyStore(getPreKeysPath(dataPath, accountPath)));
919 }
920
921 private SignedPreKeyStore getSignedPreKeyStore() {
922 return getOrCreate(() -> signedPreKeyStore,
923 () -> signedPreKeyStore = new SignedPreKeyStore(getSignedPreKeysPath(dataPath, accountPath)));
924 }
925
926 public SessionStore getSessionStore() {
927 return getOrCreate(() -> sessionStore,
928 () -> sessionStore = new SessionStore(getSessionsPath(dataPath, accountPath), getRecipientStore()));
929 }
930
931 public IdentityKeyStore getIdentityKeyStore() {
932 return getOrCreate(() -> identityKeyStore,
933 () -> identityKeyStore = new IdentityKeyStore(getIdentitiesPath(dataPath, accountPath),
934 getRecipientStore(),
935 aciIdentityKeyPair,
936 localRegistrationId,
937 trustNewIdentity));
938 }
939
940 public GroupStore getGroupStore() {
941 return groupStore;
942 }
943
944 public ContactsStore getContactStore() {
945 return getRecipientStore();
946 }
947
948 public RecipientStore getRecipientStore() {
949 return getOrCreate(() -> recipientStore,
950 () -> recipientStore = RecipientStore.load(getRecipientsStoreFile(dataPath, accountPath),
951 this::mergeRecipients,
952 this::getSelfRecipientAddress));
953 }
954
955 public ProfileStore getProfileStore() {
956 return getRecipientStore();
957 }
958
959 public StickerStore getStickerStore() {
960 return stickerStore;
961 }
962
963 public SenderKeyStore getSenderKeyStore() {
964 return getOrCreate(() -> senderKeyStore,
965 () -> senderKeyStore = new SenderKeyStore(getSharedSenderKeysFile(dataPath, accountPath),
966 getSenderKeysPath(dataPath, accountPath),
967 getRecipientStore()::resolveRecipientAddress,
968 getRecipientStore()));
969 }
970
971 public ConfigurationStore getConfigurationStore() {
972 return configurationStore;
973 }
974
975 public MessageCache getMessageCache() {
976 return getOrCreate(() -> messageCache,
977 () -> messageCache = new MessageCache(getMessageCachePath(dataPath, accountPath)));
978 }
979
980 public AccountDatabase getAccountDatabase() {
981 return getOrCreate(() -> accountDatabase, () -> {
982 try {
983 accountDatabase = AccountDatabase.init(getDatabaseFile(dataPath, accountPath));
984 } catch (SQLException e) {
985 throw new RuntimeException(e);
986 }
987 });
988 }
989
990 public MessageSendLogStore getMessageSendLogStore() {
991 return getOrCreate(() -> messageSendLogStore,
992 () -> messageSendLogStore = new MessageSendLogStore(getRecipientStore(), getAccountDatabase()));
993 }
994
995 public CredentialsProvider getCredentialsProvider() {
996 return new CredentialsProvider() {
997 @Override
998 public ACI getAci() {
999 return aci;
1000 }
1001
1002 @Override
1003 public PNI getPni() {
1004 return pni;
1005 }
1006
1007 @Override
1008 public String getE164() {
1009 return number;
1010 }
1011
1012 @Override
1013 public String getPassword() {
1014 return password;
1015 }
1016
1017 @Override
1018 public int getDeviceId() {
1019 return deviceId;
1020 }
1021 };
1022 }
1023
1024 public String getNumber() {
1025 return number;
1026 }
1027
1028 public void setNumber(final String number) {
1029 this.number = number;
1030 save();
1031 }
1032
1033 public ACI getAci() {
1034 return aci;
1035 }
1036
1037 public void setAci(final ACI aci) {
1038 this.aci = aci;
1039 save();
1040 }
1041
1042 public PNI getPni() {
1043 return pni;
1044 }
1045
1046 public void setPni(final PNI pni) {
1047 this.pni = pni;
1048 save();
1049 }
1050
1051 public SignalServiceAddress getSelfAddress() {
1052 return new SignalServiceAddress(aci, number);
1053 }
1054
1055 public RecipientAddress getSelfRecipientAddress() {
1056 return new RecipientAddress(aci == null ? null : aci.uuid(), number);
1057 }
1058
1059 public RecipientId getSelfRecipientId() {
1060 return getRecipientStore().resolveRecipient(getSelfRecipientAddress());
1061 }
1062
1063 public byte[] getEncryptedDeviceName() {
1064 return encryptedDeviceName == null ? null : Base64.getDecoder().decode(encryptedDeviceName);
1065 }
1066
1067 public void setEncryptedDeviceName(final String encryptedDeviceName) {
1068 this.encryptedDeviceName = encryptedDeviceName;
1069 save();
1070 }
1071
1072 public int getDeviceId() {
1073 return deviceId;
1074 }
1075
1076 public boolean isMasterDevice() {
1077 return deviceId == SignalServiceAddress.DEFAULT_DEVICE_ID;
1078 }
1079
1080 public IdentityKeyPair getAciIdentityKeyPair() {
1081 return aciIdentityKeyPair;
1082 }
1083
1084 public IdentityKeyPair getPniIdentityKeyPair() {
1085 return pniIdentityKeyPair;
1086 }
1087
1088 public void setPniIdentityKeyPair(final IdentityKeyPair identityKeyPair) {
1089 pniIdentityKeyPair = identityKeyPair;
1090 save();
1091 }
1092
1093 public int getLocalRegistrationId() {
1094 return localRegistrationId;
1095 }
1096
1097 public String getPassword() {
1098 return password;
1099 }
1100
1101 private void setPassword(final String password) {
1102 this.password = password;
1103 save();
1104 }
1105
1106 public void setRegistrationLockPin(final String registrationLockPin, final MasterKey pinMasterKey) {
1107 this.registrationLockPin = registrationLockPin;
1108 this.pinMasterKey = pinMasterKey;
1109 save();
1110 }
1111
1112 public MasterKey getPinMasterKey() {
1113 return pinMasterKey;
1114 }
1115
1116 public StorageKey getStorageKey() {
1117 if (pinMasterKey != null) {
1118 return pinMasterKey.deriveStorageServiceKey();
1119 }
1120 return storageKey;
1121 }
1122
1123 public void setStorageKey(final StorageKey storageKey) {
1124 if (storageKey.equals(this.storageKey)) {
1125 return;
1126 }
1127 this.storageKey = storageKey;
1128 save();
1129 }
1130
1131 public long getStorageManifestVersion() {
1132 return this.storageManifestVersion;
1133 }
1134
1135 public void setStorageManifestVersion(final long storageManifestVersion) {
1136 if (storageManifestVersion == this.storageManifestVersion) {
1137 return;
1138 }
1139 this.storageManifestVersion = storageManifestVersion;
1140 save();
1141 }
1142
1143 public ProfileKey getProfileKey() {
1144 return profileKey;
1145 }
1146
1147 public void setProfileKey(final ProfileKey profileKey) {
1148 if (profileKey.equals(this.profileKey)) {
1149 return;
1150 }
1151 this.profileKey = profileKey;
1152 save();
1153 }
1154
1155 public byte[] getSelfUnidentifiedAccessKey() {
1156 return UnidentifiedAccess.deriveAccessKeyFrom(getProfileKey());
1157 }
1158
1159 public int getPreKeyIdOffset() {
1160 return preKeyIdOffset;
1161 }
1162
1163 public int getNextSignedPreKeyId() {
1164 return nextSignedPreKeyId;
1165 }
1166
1167 public boolean isRegistered() {
1168 return registered;
1169 }
1170
1171 public void setRegistered(final boolean registered) {
1172 this.registered = registered;
1173 save();
1174 }
1175
1176 public boolean isMultiDevice() {
1177 return isMultiDevice;
1178 }
1179
1180 public void setMultiDevice(final boolean multiDevice) {
1181 if (isMultiDevice == multiDevice) {
1182 return;
1183 }
1184 isMultiDevice = multiDevice;
1185 save();
1186 }
1187
1188 public long getLastReceiveTimestamp() {
1189 return lastReceiveTimestamp;
1190 }
1191
1192 public void setLastReceiveTimestamp(final long lastReceiveTimestamp) {
1193 this.lastReceiveTimestamp = lastReceiveTimestamp;
1194 save();
1195 }
1196
1197 public boolean isUnrestrictedUnidentifiedAccess() {
1198 // TODO make configurable
1199 return false;
1200 }
1201
1202 public boolean isDiscoverableByPhoneNumber() {
1203 return configurationStore.getPhoneNumberUnlisted() == null || !configurationStore.getPhoneNumberUnlisted();
1204 }
1205
1206 public void finishRegistration(final ACI aci, final PNI pni, final MasterKey masterKey, final String pin) {
1207 this.pinMasterKey = masterKey;
1208 this.storageManifestVersion = -1;
1209 this.storageKey = null;
1210 this.encryptedDeviceName = null;
1211 this.deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
1212 this.isMultiDevice = false;
1213 this.registered = true;
1214 this.aci = aci;
1215 this.pni = pni;
1216 this.registrationLockPin = pin;
1217 this.lastReceiveTimestamp = 0;
1218 save();
1219
1220 clearAllPreKeys();
1221 getSessionStore().archiveAllSessions();
1222 getSenderKeyStore().deleteAll();
1223 final var recipientId = getRecipientStore().resolveSelfRecipientTrusted(getSelfRecipientAddress());
1224 final var publicKey = getAciIdentityKeyPair().getPublicKey();
1225 getIdentityKeyStore().saveIdentity(recipientId, publicKey, new Date());
1226 getIdentityKeyStore().setIdentityTrustLevel(recipientId, publicKey, TrustLevel.TRUSTED_VERIFIED);
1227 }
1228
1229 @Override
1230 public void close() {
1231 synchronized (fileChannel) {
1232 if (accountDatabase != null) {
1233 try {
1234 accountDatabase.close();
1235 } catch (SQLException e) {
1236 logger.warn("Failed to close account database: {}", e.getMessage(), e);
1237 }
1238 }
1239 if (messageSendLogStore != null) {
1240 messageSendLogStore.close();
1241 }
1242 try {
1243 try {
1244 lock.close();
1245 } catch (ClosedChannelException ignored) {
1246 }
1247 fileChannel.close();
1248 } catch (IOException e) {
1249 logger.warn("Failed to close account: {}", e.getMessage(), e);
1250 }
1251 }
1252 }
1253
1254 private <T> T getOrCreate(Supplier<T> supplier, Callable creator) {
1255 var value = supplier.get();
1256 if (value != null) {
1257 return value;
1258 }
1259
1260 synchronized (LOCK) {
1261 value = supplier.get();
1262 if (value != null) {
1263 return value;
1264 }
1265 creator.call();
1266 return supplier.get();
1267 }
1268 }
1269
1270 private interface Callable {
1271
1272 void call();
1273 }
1274 }