1 package org
.asamk
.signal
.manager
.storage
;
3 import com
.fasterxml
.jackson
.databind
.JsonNode
;
4 import com
.fasterxml
.jackson
.databind
.ObjectMapper
;
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
.libsignal
.protocol
.IdentityKeyPair
;
38 import org
.signal
.libsignal
.protocol
.InvalidMessageException
;
39 import org
.signal
.libsignal
.protocol
.SignalProtocolAddress
;
40 import org
.signal
.libsignal
.protocol
.state
.PreKeyRecord
;
41 import org
.signal
.libsignal
.protocol
.state
.SessionRecord
;
42 import org
.signal
.libsignal
.protocol
.state
.SignedPreKeyRecord
;
43 import org
.signal
.libsignal
.protocol
.util
.Medium
;
44 import org
.signal
.libsignal
.zkgroup
.InvalidInputException
;
45 import org
.signal
.libsignal
.zkgroup
.profiles
.ProfileKey
;
46 import org
.slf4j
.Logger
;
47 import org
.slf4j
.LoggerFactory
;
48 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountDataStore
;
49 import org
.whispersystems
.signalservice
.api
.SignalServiceDataStore
;
50 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccess
;
51 import org
.whispersystems
.signalservice
.api
.kbs
.MasterKey
;
52 import org
.whispersystems
.signalservice
.api
.push
.ACI
;
53 import org
.whispersystems
.signalservice
.api
.push
.DistributionId
;
54 import org
.whispersystems
.signalservice
.api
.push
.PNI
;
55 import org
.whispersystems
.signalservice
.api
.push
.ServiceId
;
56 import org
.whispersystems
.signalservice
.api
.push
.ServiceIdType
;
57 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
58 import org
.whispersystems
.signalservice
.api
.storage
.StorageKey
;
59 import org
.whispersystems
.signalservice
.api
.util
.CredentialsProvider
;
60 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
62 import java
.io
.ByteArrayInputStream
;
63 import java
.io
.ByteArrayOutputStream
;
64 import java
.io
.Closeable
;
66 import java
.io
.IOException
;
67 import java
.io
.RandomAccessFile
;
68 import java
.nio
.channels
.Channels
;
69 import java
.nio
.channels
.ClosedChannelException
;
70 import java
.nio
.channels
.FileChannel
;
71 import java
.nio
.channels
.FileLock
;
72 import java
.security
.SecureRandom
;
73 import java
.sql
.SQLException
;
74 import java
.util
.Base64
;
75 import java
.util
.Date
;
76 import java
.util
.HashSet
;
77 import java
.util
.List
;
78 import java
.util
.function
.Supplier
;
80 public class SignalAccount
implements Closeable
{
82 private final static Logger logger
= LoggerFactory
.getLogger(SignalAccount
.class);
84 private static final int MINIMUM_STORAGE_VERSION
= 1;
85 private static final int CURRENT_STORAGE_VERSION
= 3;
87 private final Object LOCK
= new Object();
89 private final ObjectMapper jsonProcessor
= Utils
.createStorageObjectMapper();
91 private final FileChannel fileChannel
;
92 private final FileLock lock
;
94 private int previousStorageVersion
;
96 private File dataPath
;
97 private String accountPath
;
98 private String number
;
101 private String encryptedDeviceName
;
102 private int deviceId
= SignalServiceAddress
.DEFAULT_DEVICE_ID
;
103 private boolean isMultiDevice
= false;
104 private String password
;
105 private String registrationLockPin
;
106 private MasterKey pinMasterKey
;
107 private StorageKey storageKey
;
108 private long storageManifestVersion
= -1;
109 private ProfileKey profileKey
;
110 private int aciPreKeyIdOffset
= 1;
111 private int aciNextSignedPreKeyId
= 1;
112 private int pniPreKeyIdOffset
= 1;
113 private int pniNextSignedPreKeyId
= 1;
114 private IdentityKeyPair aciIdentityKeyPair
;
115 private IdentityKeyPair pniIdentityKeyPair
;
116 private int localRegistrationId
;
117 private TrustNewIdentity trustNewIdentity
;
118 private long lastReceiveTimestamp
= 0;
120 private boolean registered
= false;
122 private SignalProtocolStore signalProtocolStore
;
123 private PreKeyStore aciPreKeyStore
;
124 private SignedPreKeyStore aciSignedPreKeyStore
;
125 private PreKeyStore pniPreKeyStore
;
126 private SignedPreKeyStore pniSignedPreKeyStore
;
127 private SessionStore sessionStore
;
128 private IdentityKeyStore identityKeyStore
;
129 private SenderKeyStore senderKeyStore
;
130 private GroupStore groupStore
;
131 private GroupStore
.Storage groupStoreStorage
;
132 private RecipientStore recipientStore
;
133 private StickerStore stickerStore
;
134 private StickerStore
.Storage stickerStoreStorage
;
135 private ConfigurationStore configurationStore
;
136 private ConfigurationStore
.Storage configurationStoreStorage
;
138 private MessageCache messageCache
;
139 private MessageSendLogStore messageSendLogStore
;
141 private AccountDatabase accountDatabase
;
143 private SignalAccount(final FileChannel fileChannel
, final FileLock lock
) {
144 this.fileChannel
= fileChannel
;
148 public static SignalAccount
load(
149 File dataPath
, String accountPath
, boolean waitForLock
, final TrustNewIdentity trustNewIdentity
150 ) throws IOException
{
151 logger
.trace("Opening account file");
152 final var fileName
= getFileName(dataPath
, accountPath
);
153 final var pair
= openFileChannel(fileName
, waitForLock
);
155 var signalAccount
= new SignalAccount(pair
.first(), pair
.second());
156 logger
.trace("Loading account file");
157 signalAccount
.load(dataPath
, accountPath
, trustNewIdentity
);
158 logger
.trace("Migrating legacy parts of account file");
159 signalAccount
.migrateLegacyConfigs();
161 return signalAccount
;
162 } catch (Throwable e
) {
163 pair
.second().close();
164 pair
.first().close();
169 public static SignalAccount
create(
173 IdentityKeyPair aciIdentityKey
,
174 IdentityKeyPair pniIdentityKey
,
176 ProfileKey profileKey
,
177 final TrustNewIdentity trustNewIdentity
178 ) throws IOException
{
179 IOUtils
.createPrivateDirectories(dataPath
);
180 var fileName
= getFileName(dataPath
, accountPath
);
181 if (!fileName
.exists()) {
182 IOUtils
.createPrivateFile(fileName
);
185 final var pair
= openFileChannel(fileName
, true);
186 var signalAccount
= new SignalAccount(pair
.first(), pair
.second());
188 signalAccount
.accountPath
= accountPath
;
189 signalAccount
.number
= number
;
190 signalAccount
.profileKey
= profileKey
;
192 signalAccount
.dataPath
= dataPath
;
193 signalAccount
.aciIdentityKeyPair
= aciIdentityKey
;
194 signalAccount
.pniIdentityKeyPair
= pniIdentityKey
;
195 signalAccount
.localRegistrationId
= registrationId
;
196 signalAccount
.trustNewIdentity
= trustNewIdentity
;
197 signalAccount
.groupStore
= new GroupStore(getGroupCachePath(dataPath
, accountPath
),
198 signalAccount
.getRecipientStore(),
199 signalAccount
::saveGroupStore
);
200 signalAccount
.stickerStore
= new StickerStore(signalAccount
::saveStickerStore
);
201 signalAccount
.configurationStore
= new ConfigurationStore(signalAccount
::saveConfigurationStore
);
203 signalAccount
.registered
= false;
205 signalAccount
.previousStorageVersion
= CURRENT_STORAGE_VERSION
;
206 signalAccount
.migrateLegacyConfigs();
207 signalAccount
.save();
209 return signalAccount
;
212 public static SignalAccount
createOrUpdateLinkedAccount(
219 String encryptedDeviceName
,
221 IdentityKeyPair aciIdentityKey
,
222 IdentityKeyPair pniIdentityKey
,
224 ProfileKey profileKey
,
225 final TrustNewIdentity trustNewIdentity
226 ) throws IOException
{
227 IOUtils
.createPrivateDirectories(dataPath
);
228 var fileName
= getFileName(dataPath
, accountPath
);
229 if (!fileName
.exists()) {
230 return createLinkedAccount(dataPath
,
245 final var signalAccount
= load(dataPath
, accountPath
, true, trustNewIdentity
);
246 signalAccount
.setProvisioningData(number
,
255 signalAccount
.getRecipientStore().resolveSelfRecipientTrusted(signalAccount
.getSelfRecipientAddress());
256 signalAccount
.getSessionStore().archiveAllSessions();
257 signalAccount
.getSenderKeyStore().deleteAll();
258 signalAccount
.clearAllPreKeys();
259 return signalAccount
;
262 public void initDatabase() {
263 getAccountDatabase();
266 private void clearAllPreKeys() {
267 this.aciPreKeyIdOffset
= new SecureRandom().nextInt(Medium
.MAX_VALUE
);
268 this.aciNextSignedPreKeyId
= new SecureRandom().nextInt(Medium
.MAX_VALUE
);
269 this.pniPreKeyIdOffset
= new SecureRandom().nextInt(Medium
.MAX_VALUE
);
270 this.pniNextSignedPreKeyId
= new SecureRandom().nextInt(Medium
.MAX_VALUE
);
271 this.getAciPreKeyStore().removeAllPreKeys();
272 this.getAciSignedPreKeyStore().removeAllSignedPreKeys();
273 this.getPniPreKeyStore().removeAllPreKeys();
274 this.getPniSignedPreKeyStore().removeAllSignedPreKeys();
278 private static SignalAccount
createLinkedAccount(
285 String encryptedDeviceName
,
287 IdentityKeyPair aciIdentityKey
,
288 IdentityKeyPair pniIdentityKey
,
290 ProfileKey profileKey
,
291 final TrustNewIdentity trustNewIdentity
292 ) throws IOException
{
293 var fileName
= getFileName(dataPath
, accountPath
);
294 IOUtils
.createPrivateFile(fileName
);
296 final var pair
= openFileChannel(fileName
, true);
297 var signalAccount
= new SignalAccount(pair
.first(), pair
.second());
299 signalAccount
.setProvisioningData(number
,
309 signalAccount
.dataPath
= dataPath
;
310 signalAccount
.accountPath
= accountPath
;
311 signalAccount
.localRegistrationId
= registrationId
;
312 signalAccount
.trustNewIdentity
= trustNewIdentity
;
313 signalAccount
.groupStore
= new GroupStore(getGroupCachePath(dataPath
, accountPath
),
314 signalAccount
.getRecipientStore(),
315 signalAccount
::saveGroupStore
);
316 signalAccount
.stickerStore
= new StickerStore(signalAccount
::saveStickerStore
);
317 signalAccount
.configurationStore
= new ConfigurationStore(signalAccount
::saveConfigurationStore
);
319 signalAccount
.getRecipientStore().resolveSelfRecipientTrusted(signalAccount
.getSelfRecipientAddress());
320 signalAccount
.previousStorageVersion
= CURRENT_STORAGE_VERSION
;
321 signalAccount
.migrateLegacyConfigs();
322 signalAccount
.clearAllPreKeys();
323 signalAccount
.save();
325 return signalAccount
;
328 private void setProvisioningData(
332 final String password
,
333 final String encryptedDeviceName
,
335 final IdentityKeyPair aciIdentity
,
336 final IdentityKeyPair pniIdentity
,
337 final ProfileKey profileKey
339 this.number
= number
;
342 this.password
= password
;
343 this.profileKey
= profileKey
;
344 this.encryptedDeviceName
= encryptedDeviceName
;
345 this.deviceId
= deviceId
;
346 this.aciIdentityKeyPair
= aciIdentity
;
347 this.pniIdentityKeyPair
= pniIdentity
;
348 this.registered
= true;
349 this.isMultiDevice
= true;
350 this.lastReceiveTimestamp
= 0;
351 this.pinMasterKey
= null;
352 this.storageManifestVersion
= -1;
353 this.storageKey
= null;
356 private void migrateLegacyConfigs() {
357 if (getPassword() == null) {
358 setPassword(KeyUtils
.createPassword());
361 if (getProfileKey() == null) {
362 // Old config file, creating new profile key
363 setProfileKey(KeyUtils
.createProfileKey());
365 // Ensure our profile key is stored in profile store
366 getProfileStore().storeProfileKey(getSelfRecipientId(), getProfileKey());
367 if (previousStorageVersion
< 3) {
368 for (final var group
: groupStore
.getGroups()) {
369 if (group
instanceof GroupInfoV2
&& group
.getDistributionId() == null) {
370 ((GroupInfoV2
) group
).setDistributionId(DistributionId
.create());
371 groupStore
.updateGroup(group
);
376 if (isMasterDevice() && getPniIdentityKeyPair() == null) {
377 setPniIdentityKeyPair(KeyUtils
.generateIdentityKeyPair());
381 private void mergeRecipients(RecipientId recipientId
, RecipientId toBeMergedRecipientId
) {
382 getSessionStore().mergeRecipients(recipientId
, toBeMergedRecipientId
);
383 getIdentityKeyStore().mergeRecipients(recipientId
, toBeMergedRecipientId
);
384 getMessageCache().mergeRecipients(recipientId
, toBeMergedRecipientId
);
385 getGroupStore().mergeRecipients(recipientId
, toBeMergedRecipientId
);
386 getSenderKeyStore().mergeRecipients(recipientId
, toBeMergedRecipientId
);
389 public void removeRecipient(final RecipientId recipientId
) {
390 getSessionStore().deleteAllSessions(recipientId
);
391 getIdentityKeyStore().deleteIdentity(recipientId
);
392 getMessageCache().deleteMessages(recipientId
);
393 getSenderKeyStore().deleteAll(recipientId
);
394 getRecipientStore().deleteRecipientData(recipientId
);
397 public static File
getFileName(File dataPath
, String account
) {
398 return new File(dataPath
, account
);
401 private static File
getUserPath(final File dataPath
, final String account
) {
402 final var path
= new File(dataPath
, account
+ ".d");
404 IOUtils
.createPrivateDirectories(path
);
405 } catch (IOException e
) {
406 throw new AssertionError("Failed to create user path", e
);
411 private static File
getMessageCachePath(File dataPath
, String account
) {
412 return new File(getUserPath(dataPath
, account
), "msg-cache");
415 private static File
getGroupCachePath(File dataPath
, String account
) {
416 return new File(getUserPath(dataPath
, account
), "group-cache");
419 private static File
getAciPreKeysPath(File dataPath
, String account
) {
420 return new File(getUserPath(dataPath
, account
), "pre-keys");
423 private static File
getAciSignedPreKeysPath(File dataPath
, String account
) {
424 return new File(getUserPath(dataPath
, account
), "signed-pre-keys");
427 private static File
getPniPreKeysPath(File dataPath
, String account
) {
428 return new File(getUserPath(dataPath
, account
), "pre-keys-pni");
431 private static File
getPniSignedPreKeysPath(File dataPath
, String account
) {
432 return new File(getUserPath(dataPath
, account
), "signed-pre-keys-pni");
435 private static File
getIdentitiesPath(File dataPath
, String account
) {
436 return new File(getUserPath(dataPath
, account
), "identities");
439 private static File
getSessionsPath(File dataPath
, String account
) {
440 return new File(getUserPath(dataPath
, account
), "sessions");
443 private static File
getSenderKeysPath(File dataPath
, String account
) {
444 return new File(getUserPath(dataPath
, account
), "sender-keys");
447 private static File
getSharedSenderKeysFile(File dataPath
, String account
) {
448 return new File(getUserPath(dataPath
, account
), "shared-sender-keys-store");
451 private static File
getRecipientsStoreFile(File dataPath
, String account
) {
452 return new File(getUserPath(dataPath
, account
), "recipients-store");
455 private static File
getDatabaseFile(File dataPath
, String account
) {
456 return new File(getUserPath(dataPath
, account
), "account.db");
459 public static boolean accountFileExists(File dataPath
, String account
) {
460 if (account
== null) {
463 var f
= getFileName(dataPath
, account
);
464 return !(!f
.exists() || f
.isDirectory());
468 File dataPath
, String accountPath
, final TrustNewIdentity trustNewIdentity
469 ) throws IOException
{
470 this.dataPath
= dataPath
;
471 this.accountPath
= accountPath
;
472 final JsonNode rootNode
;
473 synchronized (fileChannel
) {
474 fileChannel
.position(0);
475 rootNode
= jsonProcessor
.readTree(Channels
.newInputStream(fileChannel
));
478 if (rootNode
.hasNonNull("version")) {
479 var accountVersion
= rootNode
.get("version").asInt(1);
480 if (accountVersion
> CURRENT_STORAGE_VERSION
) {
481 throw new IOException("Config file was created by a more recent version!");
482 } else if (accountVersion
< MINIMUM_STORAGE_VERSION
) {
483 throw new IOException("Config file was created by a no longer supported older version!");
485 previousStorageVersion
= accountVersion
;
488 number
= Utils
.getNotNullNode(rootNode
, "username").asText();
489 if (rootNode
.hasNonNull("password")) {
490 password
= rootNode
.get("password").asText();
492 registered
= Utils
.getNotNullNode(rootNode
, "registered").asBoolean();
493 if (rootNode
.hasNonNull("uuid")) {
495 aci
= ACI
.parseOrThrow(rootNode
.get("uuid").asText());
496 } catch (IllegalArgumentException e
) {
497 throw new IOException("Config file contains an invalid aci/uuid, needs to be a valid UUID", e
);
500 if (rootNode
.hasNonNull("pni")) {
502 pni
= PNI
.parseOrThrow(rootNode
.get("pni").asText());
503 } catch (IllegalArgumentException e
) {
504 throw new IOException("Config file contains an invalid pni, needs to be a valid UUID", e
);
507 if (rootNode
.hasNonNull("deviceName")) {
508 encryptedDeviceName
= rootNode
.get("deviceName").asText();
510 if (rootNode
.hasNonNull("deviceId")) {
511 deviceId
= rootNode
.get("deviceId").asInt();
513 if (rootNode
.hasNonNull("isMultiDevice")) {
514 isMultiDevice
= rootNode
.get("isMultiDevice").asBoolean();
516 if (rootNode
.hasNonNull("lastReceiveTimestamp")) {
517 lastReceiveTimestamp
= rootNode
.get("lastReceiveTimestamp").asLong();
519 int registrationId
= 0;
520 if (rootNode
.hasNonNull("registrationId")) {
521 registrationId
= rootNode
.get("registrationId").asInt();
523 IdentityKeyPair aciIdentityKeyPair
= null;
524 if (rootNode
.hasNonNull("identityPrivateKey") && rootNode
.hasNonNull("identityKey")) {
525 final var publicKeyBytes
= Base64
.getDecoder().decode(rootNode
.get("identityKey").asText());
526 final var privateKeyBytes
= Base64
.getDecoder().decode(rootNode
.get("identityPrivateKey").asText());
527 aciIdentityKeyPair
= KeyUtils
.getIdentityKeyPair(publicKeyBytes
, privateKeyBytes
);
529 if (rootNode
.hasNonNull("pniIdentityPrivateKey") && rootNode
.hasNonNull("pniIdentityKey")) {
530 final var publicKeyBytes
= Base64
.getDecoder().decode(rootNode
.get("pniIdentityKey").asText());
531 final var privateKeyBytes
= Base64
.getDecoder().decode(rootNode
.get("pniIdentityPrivateKey").asText());
532 pniIdentityKeyPair
= KeyUtils
.getIdentityKeyPair(publicKeyBytes
, privateKeyBytes
);
535 if (rootNode
.hasNonNull("registrationLockPin")) {
536 registrationLockPin
= rootNode
.get("registrationLockPin").asText();
538 if (rootNode
.hasNonNull("pinMasterKey")) {
539 pinMasterKey
= new MasterKey(Base64
.getDecoder().decode(rootNode
.get("pinMasterKey").asText()));
541 if (rootNode
.hasNonNull("storageKey")) {
542 storageKey
= new StorageKey(Base64
.getDecoder().decode(rootNode
.get("storageKey").asText()));
544 if (rootNode
.hasNonNull("storageManifestVersion")) {
545 storageManifestVersion
= rootNode
.get("storageManifestVersion").asLong();
547 if (rootNode
.hasNonNull("preKeyIdOffset")) {
548 aciPreKeyIdOffset
= rootNode
.get("preKeyIdOffset").asInt(1);
550 aciPreKeyIdOffset
= 1;
552 if (rootNode
.hasNonNull("nextSignedPreKeyId")) {
553 aciNextSignedPreKeyId
= rootNode
.get("nextSignedPreKeyId").asInt(1);
555 aciNextSignedPreKeyId
= 1;
557 if (rootNode
.hasNonNull("pniPreKeyIdOffset")) {
558 pniPreKeyIdOffset
= rootNode
.get("pniPreKeyIdOffset").asInt(1);
560 pniPreKeyIdOffset
= 1;
562 if (rootNode
.hasNonNull("pniNextSignedPreKeyId")) {
563 pniNextSignedPreKeyId
= rootNode
.get("pniNextSignedPreKeyId").asInt(1);
565 pniNextSignedPreKeyId
= 1;
567 if (rootNode
.hasNonNull("profileKey")) {
569 profileKey
= new ProfileKey(Base64
.getDecoder().decode(rootNode
.get("profileKey").asText()));
570 } catch (InvalidInputException e
) {
571 throw new IOException(
572 "Config file contains an invalid profileKey, needs to be base64 encoded array of 32 bytes",
577 var migratedLegacyConfig
= false;
578 final var legacySignalProtocolStore
= rootNode
.hasNonNull("axolotlStore")
579 ? jsonProcessor
.convertValue(Utils
.getNotNullNode(rootNode
, "axolotlStore"),
580 LegacyJsonSignalProtocolStore
.class)
582 if (legacySignalProtocolStore
!= null && legacySignalProtocolStore
.getLegacyIdentityKeyStore() != null) {
583 aciIdentityKeyPair
= legacySignalProtocolStore
.getLegacyIdentityKeyStore().getIdentityKeyPair();
584 registrationId
= legacySignalProtocolStore
.getLegacyIdentityKeyStore().getLocalRegistrationId();
585 migratedLegacyConfig
= true;
588 this.aciIdentityKeyPair
= aciIdentityKeyPair
;
589 this.localRegistrationId
= registrationId
;
590 this.trustNewIdentity
= trustNewIdentity
;
592 migratedLegacyConfig
= loadLegacyStores(rootNode
, legacySignalProtocolStore
) || migratedLegacyConfig
;
594 if (rootNode
.hasNonNull("groupStore")) {
595 groupStoreStorage
= jsonProcessor
.convertValue(rootNode
.get("groupStore"), GroupStore
.Storage
.class);
596 groupStore
= GroupStore
.fromStorage(groupStoreStorage
,
597 getGroupCachePath(dataPath
, accountPath
),
599 this::saveGroupStore
);
601 groupStore
= new GroupStore(getGroupCachePath(dataPath
, accountPath
),
603 this::saveGroupStore
);
606 if (rootNode
.hasNonNull("stickerStore")) {
607 stickerStoreStorage
= jsonProcessor
.convertValue(rootNode
.get("stickerStore"), StickerStore
.Storage
.class);
608 stickerStore
= StickerStore
.fromStorage(stickerStoreStorage
, this::saveStickerStore
);
610 stickerStore
= new StickerStore(this::saveStickerStore
);
613 if (rootNode
.hasNonNull("configurationStore")) {
614 configurationStoreStorage
= jsonProcessor
.convertValue(rootNode
.get("configurationStore"),
615 ConfigurationStore
.Storage
.class);
616 configurationStore
= ConfigurationStore
.fromStorage(configurationStoreStorage
,
617 this::saveConfigurationStore
);
619 configurationStore
= new ConfigurationStore(this::saveConfigurationStore
);
622 migratedLegacyConfig
= loadLegacyThreadStore(rootNode
) || migratedLegacyConfig
;
624 if (migratedLegacyConfig
) {
629 private boolean loadLegacyStores(
630 final JsonNode rootNode
, final LegacyJsonSignalProtocolStore legacySignalProtocolStore
632 var migrated
= false;
633 var legacyRecipientStoreNode
= rootNode
.get("recipientStore");
634 if (legacyRecipientStoreNode
!= null) {
635 logger
.debug("Migrating legacy recipient store.");
636 var legacyRecipientStore
= jsonProcessor
.convertValue(legacyRecipientStoreNode
, LegacyRecipientStore
.class);
637 if (legacyRecipientStore
!= null) {
638 getRecipientStore().resolveRecipientsTrusted(legacyRecipientStore
.getAddresses());
640 getRecipientStore().resolveSelfRecipientTrusted(getSelfRecipientAddress());
644 if (legacySignalProtocolStore
!= null && legacySignalProtocolStore
.getLegacyPreKeyStore() != null) {
645 logger
.debug("Migrating legacy pre key store.");
646 for (var entry
: legacySignalProtocolStore
.getLegacyPreKeyStore().getPreKeys().entrySet()) {
648 getAciPreKeyStore().storePreKey(entry
.getKey(), new PreKeyRecord(entry
.getValue()));
649 } catch (InvalidMessageException e
) {
650 logger
.warn("Failed to migrate pre key, ignoring", e
);
656 if (legacySignalProtocolStore
!= null && legacySignalProtocolStore
.getLegacySignedPreKeyStore() != null) {
657 logger
.debug("Migrating legacy signed pre key store.");
658 for (var entry
: legacySignalProtocolStore
.getLegacySignedPreKeyStore().getSignedPreKeys().entrySet()) {
660 getAciSignedPreKeyStore().storeSignedPreKey(entry
.getKey(),
661 new SignedPreKeyRecord(entry
.getValue()));
662 } catch (InvalidMessageException e
) {
663 logger
.warn("Failed to migrate signed pre key, ignoring", e
);
669 if (legacySignalProtocolStore
!= null && legacySignalProtocolStore
.getLegacySessionStore() != null) {
670 logger
.debug("Migrating legacy session store.");
671 for (var session
: legacySignalProtocolStore
.getLegacySessionStore().getSessions()) {
673 getSessionStore().storeSession(new SignalProtocolAddress(session
.address
.getIdentifier(),
674 session
.deviceId
), new SessionRecord(session
.sessionRecord
));
675 } catch (Exception e
) {
676 logger
.warn("Failed to migrate session, ignoring", e
);
682 if (legacySignalProtocolStore
!= null && legacySignalProtocolStore
.getLegacyIdentityKeyStore() != null) {
683 logger
.debug("Migrating legacy identity session store.");
684 for (var identity
: legacySignalProtocolStore
.getLegacyIdentityKeyStore().getIdentities()) {
685 RecipientId recipientId
= getRecipientStore().resolveRecipientTrusted(identity
.getAddress());
686 getIdentityKeyStore().saveIdentity(recipientId
, identity
.getIdentityKey(), identity
.getDateAdded());
687 getIdentityKeyStore().setIdentityTrustLevel(recipientId
,
688 identity
.getIdentityKey(),
689 identity
.getTrustLevel());
694 if (rootNode
.hasNonNull("contactStore")) {
695 logger
.debug("Migrating legacy contact store.");
696 final var contactStoreNode
= rootNode
.get("contactStore");
697 final var contactStore
= jsonProcessor
.convertValue(contactStoreNode
, LegacyJsonContactsStore
.class);
698 for (var contact
: contactStore
.getContacts()) {
699 final var recipientId
= getRecipientStore().resolveRecipientTrusted(contact
.getAddress());
700 getRecipientStore().storeContact(recipientId
,
701 new Contact(contact
.name
,
703 contact
.messageExpirationTime
,
707 // Store profile keys only in profile store
708 var profileKeyString
= contact
.profileKey
;
709 if (profileKeyString
!= null) {
710 final ProfileKey profileKey
;
712 profileKey
= new ProfileKey(Base64
.getDecoder().decode(profileKeyString
));
713 getProfileStore().storeProfileKey(recipientId
, profileKey
);
714 } catch (InvalidInputException e
) {
715 logger
.warn("Failed to parse legacy contact profile key: {}", e
.getMessage());
722 if (rootNode
.hasNonNull("profileStore")) {
723 logger
.debug("Migrating legacy profile store.");
724 var profileStoreNode
= rootNode
.get("profileStore");
725 final var legacyProfileStore
= jsonProcessor
.convertValue(profileStoreNode
, LegacyProfileStore
.class);
726 for (var profileEntry
: legacyProfileStore
.getProfileEntries()) {
727 var recipientId
= getRecipientStore().resolveRecipient(profileEntry
.getAddress());
728 getRecipientStore().storeProfileKeyCredential(recipientId
, profileEntry
.getProfileKeyCredential());
729 getRecipientStore().storeProfileKey(recipientId
, profileEntry
.getProfileKey());
730 final var profile
= profileEntry
.getProfile();
731 if (profile
!= null) {
732 final var capabilities
= new HashSet
<Profile
.Capability
>();
733 if (profile
.getCapabilities() != null) {
734 if (profile
.getCapabilities().gv1Migration
) {
735 capabilities
.add(Profile
.Capability
.gv1Migration
);
737 if (profile
.getCapabilities().storage
) {
738 capabilities
.add(Profile
.Capability
.storage
);
741 final var newProfile
= new Profile(profileEntry
.getLastUpdateTimestamp(),
742 profile
.getGivenName(),
743 profile
.getFamilyName(),
745 profile
.getAboutEmoji(),
747 profile
.isUnrestrictedUnidentifiedAccess()
748 ? Profile
.UnidentifiedAccessMode
.UNRESTRICTED
749 : profile
.getUnidentifiedAccess() != null
750 ? Profile
.UnidentifiedAccessMode
.ENABLED
751 : Profile
.UnidentifiedAccessMode
.DISABLED
,
753 getRecipientStore().storeProfile(recipientId
, newProfile
);
761 private boolean loadLegacyThreadStore(final JsonNode rootNode
) {
762 var threadStoreNode
= rootNode
.get("threadStore");
763 if (threadStoreNode
!= null && !threadStoreNode
.isNull()) {
764 var threadStore
= jsonProcessor
.convertValue(threadStoreNode
, LegacyJsonThreadStore
.class);
765 // Migrate thread info to group and contact store
766 for (var thread
: threadStore
.getThreads()) {
767 if (thread
.id
== null || thread
.id
.isEmpty()) {
771 if (UuidUtil
.isUuid(thread
.id
) || thread
.id
.startsWith("+")) {
772 final var recipientId
= getRecipientStore().resolveRecipient(thread
.id
);
773 var contact
= getRecipientStore().getContact(recipientId
);
774 if (contact
!= null) {
775 getRecipientStore().storeContact(recipientId
,
776 Contact
.newBuilder(contact
)
777 .withMessageExpirationTime(thread
.messageExpirationTime
)
781 var groupInfo
= groupStore
.getGroup(GroupId
.fromBase64(thread
.id
));
782 if (groupInfo
instanceof GroupInfoV1
) {
783 ((GroupInfoV1
) groupInfo
).messageExpirationTime
= thread
.messageExpirationTime
;
784 groupStore
.updateGroup(groupInfo
);
787 } catch (Exception e
) {
788 logger
.warn("Failed to read legacy thread info: {}", e
.getMessage());
797 private void saveStickerStore(StickerStore
.Storage storage
) {
798 this.stickerStoreStorage
= storage
;
802 private void saveGroupStore(GroupStore
.Storage storage
) {
803 this.groupStoreStorage
= storage
;
807 private void saveConfigurationStore(ConfigurationStore
.Storage storage
) {
808 this.configurationStoreStorage
= storage
;
812 private void save() {
813 synchronized (fileChannel
) {
814 var rootNode
= jsonProcessor
.createObjectNode();
815 rootNode
.put("version", CURRENT_STORAGE_VERSION
)
816 .put("username", number
)
817 .put("uuid", aci
== null ?
null : aci
.toString())
818 .put("pni", pni
== null ?
null : pni
.toString())
819 .put("deviceName", encryptedDeviceName
)
820 .put("deviceId", deviceId
)
821 .put("isMultiDevice", isMultiDevice
)
822 .put("lastReceiveTimestamp", lastReceiveTimestamp
)
823 .put("password", password
)
824 .put("registrationId", localRegistrationId
)
825 .put("identityPrivateKey",
826 Base64
.getEncoder().encodeToString(aciIdentityKeyPair
.getPrivateKey().serialize()))
828 Base64
.getEncoder().encodeToString(aciIdentityKeyPair
.getPublicKey().serialize()))
829 .put("pniIdentityPrivateKey",
830 pniIdentityKeyPair
== null
832 : Base64
.getEncoder()
833 .encodeToString(pniIdentityKeyPair
.getPrivateKey().serialize()))
834 .put("pniIdentityKey",
835 pniIdentityKeyPair
== null
837 : Base64
.getEncoder().encodeToString(pniIdentityKeyPair
.getPublicKey().serialize()))
838 .put("registrationLockPin", registrationLockPin
)
840 pinMasterKey
== null ?
null : Base64
.getEncoder().encodeToString(pinMasterKey
.serialize()))
842 storageKey
== null ?
null : Base64
.getEncoder().encodeToString(storageKey
.serialize()))
843 .put("storageManifestVersion", storageManifestVersion
== -1 ?
null : storageManifestVersion
)
844 .put("preKeyIdOffset", aciPreKeyIdOffset
)
845 .put("nextSignedPreKeyId", aciNextSignedPreKeyId
)
846 .put("pniPreKeyIdOffset", pniPreKeyIdOffset
)
847 .put("pniNextSignedPreKeyId", pniNextSignedPreKeyId
)
849 profileKey
== null ?
null : Base64
.getEncoder().encodeToString(profileKey
.serialize()))
850 .put("registered", registered
)
851 .putPOJO("groupStore", groupStoreStorage
)
852 .putPOJO("stickerStore", stickerStoreStorage
)
853 .putPOJO("configurationStore", configurationStoreStorage
);
855 try (var output
= new ByteArrayOutputStream()) {
856 // Write to memory first to prevent corrupting the file in case of serialization errors
857 jsonProcessor
.writeValue(output
, rootNode
);
858 var input
= new ByteArrayInputStream(output
.toByteArray());
859 fileChannel
.position(0);
860 input
.transferTo(Channels
.newOutputStream(fileChannel
));
861 fileChannel
.truncate(fileChannel
.position());
862 fileChannel
.force(false);
864 } catch (Exception e
) {
865 logger
.error("Error saving file: {}", e
.getMessage(), e
);
870 private static Pair
<FileChannel
, FileLock
> openFileChannel(File fileName
, boolean waitForLock
) throws IOException
{
871 var fileChannel
= new RandomAccessFile(fileName
, "rw").getChannel();
872 var lock
= fileChannel
.tryLock();
875 logger
.debug("Config file is in use by another instance.");
876 throw new IOException("Config file is in use by another instance.");
878 logger
.info("Config file is in use by another instance, waiting…");
879 lock
= fileChannel
.lock();
880 logger
.info("Config file lock acquired.");
882 return new Pair
<>(fileChannel
, lock
);
885 public void addPreKeys(ServiceIdType serviceIdType
, List
<PreKeyRecord
> records
) {
886 if (serviceIdType
.equals(ServiceIdType
.ACI
)) {
887 addAciPreKeys(records
);
889 addPniPreKeys(records
);
893 public void addAciPreKeys(List
<PreKeyRecord
> records
) {
894 for (var record : records
) {
895 if (aciPreKeyIdOffset
!= record.getId()) {
896 logger
.error("Invalid pre key id {}, expected {}", record.getId(), aciPreKeyIdOffset
);
897 throw new AssertionError("Invalid pre key id");
899 getAciPreKeyStore().storePreKey(record.getId(), record);
900 aciPreKeyIdOffset
= (aciPreKeyIdOffset
+ 1) % Medium
.MAX_VALUE
;
905 public void addPniPreKeys(List
<PreKeyRecord
> records
) {
906 for (var record : records
) {
907 if (pniPreKeyIdOffset
!= record.getId()) {
908 logger
.error("Invalid pre key id {}, expected {}", record.getId(), pniPreKeyIdOffset
);
909 throw new AssertionError("Invalid pre key id");
911 getPniPreKeyStore().storePreKey(record.getId(), record);
912 pniPreKeyIdOffset
= (pniPreKeyIdOffset
+ 1) % Medium
.MAX_VALUE
;
917 public void addSignedPreKey(ServiceIdType serviceIdType
, SignedPreKeyRecord
record) {
918 if (serviceIdType
.equals(ServiceIdType
.ACI
)) {
919 addAciSignedPreKey(record);
921 addPniSignedPreKey(record);
925 public void addAciSignedPreKey(SignedPreKeyRecord
record) {
926 if (aciNextSignedPreKeyId
!= record.getId()) {
927 logger
.error("Invalid signed pre key id {}, expected {}", record.getId(), aciNextSignedPreKeyId
);
928 throw new AssertionError("Invalid signed pre key id");
930 getAciSignedPreKeyStore().storeSignedPreKey(record.getId(), record);
931 aciNextSignedPreKeyId
= (aciNextSignedPreKeyId
+ 1) % Medium
.MAX_VALUE
;
935 public void addPniSignedPreKey(SignedPreKeyRecord
record) {
936 if (pniNextSignedPreKeyId
!= record.getId()) {
937 logger
.error("Invalid signed pre key id {}, expected {}", record.getId(), pniNextSignedPreKeyId
);
938 throw new AssertionError("Invalid signed pre key id");
940 getPniSignedPreKeyStore().storeSignedPreKey(record.getId(), record);
941 pniNextSignedPreKeyId
= (pniNextSignedPreKeyId
+ 1) % Medium
.MAX_VALUE
;
945 public SignalServiceDataStore
getSignalServiceDataStore() {
946 return new SignalServiceDataStore() {
948 public SignalServiceAccountDataStore
get(final ServiceId accountIdentifier
) {
949 if (accountIdentifier
.equals(aci
)) {
950 return getSignalServiceAccountDataStore();
951 } else if (accountIdentifier
.equals(pni
)) {
952 throw new AssertionError("PNI not to be used yet!");
954 throw new IllegalArgumentException("No matching store found for " + accountIdentifier
);
959 public SignalServiceAccountDataStore
aci() {
960 return getSignalServiceAccountDataStore();
964 public SignalServiceAccountDataStore
pni() {
965 throw new AssertionError("PNI not to be used yet!");
969 public boolean isMultiDevice() {
970 return SignalAccount
.this.isMultiDevice();
975 public SignalServiceAccountDataStore
getSignalServiceAccountDataStore() {
976 return getOrCreate(() -> signalProtocolStore
,
977 () -> signalProtocolStore
= new SignalProtocolStore(getAciPreKeyStore(),
978 getAciSignedPreKeyStore(),
980 getIdentityKeyStore(),
982 this::isMultiDevice
));
985 private PreKeyStore
getAciPreKeyStore() {
986 return getOrCreate(() -> aciPreKeyStore
,
987 () -> aciPreKeyStore
= new PreKeyStore(getAciPreKeysPath(dataPath
, accountPath
)));
990 private SignedPreKeyStore
getAciSignedPreKeyStore() {
991 return getOrCreate(() -> aciSignedPreKeyStore
,
992 () -> aciSignedPreKeyStore
= new SignedPreKeyStore(getAciSignedPreKeysPath(dataPath
, accountPath
)));
995 private PreKeyStore
getPniPreKeyStore() {
996 return getOrCreate(() -> pniPreKeyStore
,
997 () -> pniPreKeyStore
= new PreKeyStore(getPniPreKeysPath(dataPath
, accountPath
)));
1000 private SignedPreKeyStore
getPniSignedPreKeyStore() {
1001 return getOrCreate(() -> pniSignedPreKeyStore
,
1002 () -> pniSignedPreKeyStore
= new SignedPreKeyStore(getPniSignedPreKeysPath(dataPath
, accountPath
)));
1005 public SessionStore
getSessionStore() {
1006 return getOrCreate(() -> sessionStore
,
1007 () -> sessionStore
= new SessionStore(getSessionsPath(dataPath
, accountPath
), getRecipientStore()));
1010 public IdentityKeyStore
getIdentityKeyStore() {
1011 return getOrCreate(() -> identityKeyStore
,
1012 () -> identityKeyStore
= new IdentityKeyStore(getIdentitiesPath(dataPath
, accountPath
),
1013 getRecipientStore(),
1015 localRegistrationId
,
1019 public GroupStore
getGroupStore() {
1023 public ContactsStore
getContactStore() {
1024 return getRecipientStore();
1027 public RecipientStore
getRecipientStore() {
1028 return getOrCreate(() -> recipientStore
,
1029 () -> recipientStore
= RecipientStore
.load(getRecipientsStoreFile(dataPath
, accountPath
),
1030 this::mergeRecipients
,
1031 this::getSelfRecipientAddress
));
1034 public ProfileStore
getProfileStore() {
1035 return getRecipientStore();
1038 public StickerStore
getStickerStore() {
1039 return stickerStore
;
1042 public SenderKeyStore
getSenderKeyStore() {
1043 return getOrCreate(() -> senderKeyStore
,
1044 () -> senderKeyStore
= new SenderKeyStore(getSharedSenderKeysFile(dataPath
, accountPath
),
1045 getSenderKeysPath(dataPath
, accountPath
),
1046 getRecipientStore()::resolveRecipientAddress
,
1047 getRecipientStore()));
1050 public ConfigurationStore
getConfigurationStore() {
1051 return configurationStore
;
1054 public MessageCache
getMessageCache() {
1055 return getOrCreate(() -> messageCache
,
1056 () -> messageCache
= new MessageCache(getMessageCachePath(dataPath
, accountPath
)));
1059 public AccountDatabase
getAccountDatabase() {
1060 return getOrCreate(() -> accountDatabase
, () -> {
1062 accountDatabase
= AccountDatabase
.init(getDatabaseFile(dataPath
, accountPath
));
1063 } catch (SQLException e
) {
1064 throw new RuntimeException(e
);
1069 public MessageSendLogStore
getMessageSendLogStore() {
1070 return getOrCreate(() -> messageSendLogStore
,
1071 () -> messageSendLogStore
= new MessageSendLogStore(getRecipientStore(), getAccountDatabase()));
1074 public CredentialsProvider
getCredentialsProvider() {
1075 return new CredentialsProvider() {
1077 public ACI
getAci() {
1082 public PNI
getPni() {
1087 public String
getE164() {
1092 public String
getPassword() {
1097 public int getDeviceId() {
1103 public String
getNumber() {
1107 public void setNumber(final String number
) {
1108 this.number
= number
;
1112 public ACI
getAci() {
1116 public void setAci(final ACI aci
) {
1121 public PNI
getPni() {
1125 public void setPni(final PNI pni
) {
1130 public SignalServiceAddress
getSelfAddress() {
1131 return new SignalServiceAddress(aci
, number
);
1134 public RecipientAddress
getSelfRecipientAddress() {
1135 return new RecipientAddress(aci
== null ?
null : aci
.uuid(), number
);
1138 public RecipientId
getSelfRecipientId() {
1139 return getRecipientStore().resolveRecipient(getSelfRecipientAddress());
1142 public byte[] getEncryptedDeviceName() {
1143 return encryptedDeviceName
== null ?
null : Base64
.getDecoder().decode(encryptedDeviceName
);
1146 public void setEncryptedDeviceName(final String encryptedDeviceName
) {
1147 this.encryptedDeviceName
= encryptedDeviceName
;
1151 public int getDeviceId() {
1155 public boolean isMasterDevice() {
1156 return deviceId
== SignalServiceAddress
.DEFAULT_DEVICE_ID
;
1159 public IdentityKeyPair
getIdentityKeyPair(ServiceIdType serviceIdType
) {
1160 return serviceIdType
.equals(ServiceIdType
.ACI
) ? aciIdentityKeyPair
: pniIdentityKeyPair
;
1163 public IdentityKeyPair
getAciIdentityKeyPair() {
1164 return aciIdentityKeyPair
;
1167 public IdentityKeyPair
getPniIdentityKeyPair() {
1168 return pniIdentityKeyPair
;
1171 public void setPniIdentityKeyPair(final IdentityKeyPair identityKeyPair
) {
1172 pniIdentityKeyPair
= identityKeyPair
;
1176 public int getLocalRegistrationId() {
1177 return localRegistrationId
;
1180 public String
getPassword() {
1184 private void setPassword(final String password
) {
1185 this.password
= password
;
1189 public void setRegistrationLockPin(final String registrationLockPin
, final MasterKey pinMasterKey
) {
1190 this.registrationLockPin
= registrationLockPin
;
1191 this.pinMasterKey
= pinMasterKey
;
1195 public MasterKey
getPinMasterKey() {
1196 return pinMasterKey
;
1199 public StorageKey
getStorageKey() {
1200 if (pinMasterKey
!= null) {
1201 return pinMasterKey
.deriveStorageServiceKey();
1206 public void setStorageKey(final StorageKey storageKey
) {
1207 if (storageKey
.equals(this.storageKey
)) {
1210 this.storageKey
= storageKey
;
1214 public long getStorageManifestVersion() {
1215 return this.storageManifestVersion
;
1218 public void setStorageManifestVersion(final long storageManifestVersion
) {
1219 if (storageManifestVersion
== this.storageManifestVersion
) {
1222 this.storageManifestVersion
= storageManifestVersion
;
1226 public ProfileKey
getProfileKey() {
1230 public void setProfileKey(final ProfileKey profileKey
) {
1231 if (profileKey
.equals(this.profileKey
)) {
1234 this.profileKey
= profileKey
;
1238 public byte[] getSelfUnidentifiedAccessKey() {
1239 return UnidentifiedAccess
.deriveAccessKeyFrom(getProfileKey());
1242 public int getPreKeyIdOffset(ServiceIdType serviceIdType
) {
1243 return serviceIdType
.equals(ServiceIdType
.ACI
) ? aciPreKeyIdOffset
: pniPreKeyIdOffset
;
1246 public int getNextSignedPreKeyId(ServiceIdType serviceIdType
) {
1247 return serviceIdType
.equals(ServiceIdType
.ACI
) ? aciNextSignedPreKeyId
: pniNextSignedPreKeyId
;
1250 public boolean isRegistered() {
1254 public void setRegistered(final boolean registered
) {
1255 this.registered
= registered
;
1259 public boolean isMultiDevice() {
1260 return isMultiDevice
;
1263 public void setMultiDevice(final boolean multiDevice
) {
1264 if (isMultiDevice
== multiDevice
) {
1267 isMultiDevice
= multiDevice
;
1271 public long getLastReceiveTimestamp() {
1272 return lastReceiveTimestamp
;
1275 public void setLastReceiveTimestamp(final long lastReceiveTimestamp
) {
1276 this.lastReceiveTimestamp
= lastReceiveTimestamp
;
1280 public boolean isUnrestrictedUnidentifiedAccess() {
1281 // TODO make configurable
1285 public boolean isDiscoverableByPhoneNumber() {
1286 return configurationStore
.getPhoneNumberUnlisted() == null || !configurationStore
.getPhoneNumberUnlisted();
1289 public void finishRegistration(final ACI aci
, final PNI pni
, final MasterKey masterKey
, final String pin
) {
1290 this.pinMasterKey
= masterKey
;
1291 this.storageManifestVersion
= -1;
1292 this.storageKey
= null;
1293 this.encryptedDeviceName
= null;
1294 this.deviceId
= SignalServiceAddress
.DEFAULT_DEVICE_ID
;
1295 this.isMultiDevice
= false;
1296 this.registered
= true;
1299 this.registrationLockPin
= pin
;
1300 this.lastReceiveTimestamp
= 0;
1304 getSessionStore().archiveAllSessions();
1305 getSenderKeyStore().deleteAll();
1306 final var recipientId
= getRecipientStore().resolveSelfRecipientTrusted(getSelfRecipientAddress());
1307 final var publicKey
= getAciIdentityKeyPair().getPublicKey();
1308 getIdentityKeyStore().saveIdentity(recipientId
, publicKey
, new Date());
1309 getIdentityKeyStore().setIdentityTrustLevel(recipientId
, publicKey
, TrustLevel
.TRUSTED_VERIFIED
);
1313 public void close() {
1314 synchronized (fileChannel
) {
1315 if (accountDatabase
!= null) {
1317 accountDatabase
.close();
1318 } catch (SQLException e
) {
1319 logger
.warn("Failed to close account database: {}", e
.getMessage(), e
);
1322 if (messageSendLogStore
!= null) {
1323 messageSendLogStore
.close();
1328 } catch (ClosedChannelException ignored
) {
1330 fileChannel
.close();
1331 } catch (IOException e
) {
1332 logger
.warn("Failed to close account: {}", e
.getMessage(), e
);
1337 private <T
> T
getOrCreate(Supplier
<T
> supplier
, Callable creator
) {
1338 var value
= supplier
.get();
1339 if (value
!= null) {
1343 synchronized (LOCK
) {
1344 value
= supplier
.get();
1345 if (value
!= null) {
1349 return supplier
.get();
1353 private interface Callable
{