Create stores in SignalAccount lazily

This commit is contained in:
AsamK 2022-01-26 21:03:04 +01:00
parent e5537dc4db
commit 67146f9cc7
4 changed files with 134 additions and 94 deletions

View file

@ -66,6 +66,7 @@ import java.util.Base64;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.function.Supplier;
public class SignalAccount implements Closeable {
@ -74,13 +75,16 @@ public class SignalAccount implements Closeable {
private static final int MINIMUM_STORAGE_VERSION = 1;
private static final int CURRENT_STORAGE_VERSION = 3;
private int previousStorageVersion;
private final Object LOCK = new Object();
private final ObjectMapper jsonProcessor = Utils.createStorageObjectMapper();
private final FileChannel fileChannel;
private final FileLock lock;
private int previousStorageVersion;
private File dataPath;
private String account;
private ACI aci;
private String encryptedDeviceName;
@ -94,6 +98,9 @@ public class SignalAccount implements Closeable {
private ProfileKey profileKey;
private int preKeyIdOffset;
private int nextSignedPreKeyId;
private IdentityKeyPair identityKeyPair;
private int localRegistrationId;
private TrustNewIdentity trustNewIdentity;
private long lastReceiveTimestamp = 0;
private boolean registered = false;
@ -165,9 +172,12 @@ public class SignalAccount implements Closeable {
signalAccount.account = account;
signalAccount.profileKey = profileKey;
signalAccount.initStores(dataPath, identityKey, registrationId, trustNewIdentity);
signalAccount.dataPath = dataPath;
signalAccount.identityKeyPair = identityKey;
signalAccount.localRegistrationId = registrationId;
signalAccount.trustNewIdentity = trustNewIdentity;
signalAccount.groupStore = new GroupStore(getGroupCachePath(dataPath, account),
signalAccount.recipientStore,
signalAccount.getRecipientStore(),
signalAccount::saveGroupStore);
signalAccount.stickerStore = new StickerStore(signalAccount::saveStickerStore);
signalAccount.configurationStore = new ConfigurationStore(signalAccount::saveConfigurationStore);
@ -181,36 +191,6 @@ public class SignalAccount implements Closeable {
return signalAccount;
}
private void initStores(
final File dataPath,
final IdentityKeyPair identityKey,
final int registrationId,
final TrustNewIdentity trustNewIdentity
) throws IOException {
recipientStore = RecipientStore.load(getRecipientsStoreFile(dataPath, account), this::mergeRecipients);
preKeyStore = new PreKeyStore(getPreKeysPath(dataPath, account));
signedPreKeyStore = new SignedPreKeyStore(getSignedPreKeysPath(dataPath, account));
sessionStore = new SessionStore(getSessionsPath(dataPath, account), recipientStore);
identityKeyStore = new IdentityKeyStore(getIdentitiesPath(dataPath, account),
recipientStore,
identityKey,
registrationId,
trustNewIdentity);
senderKeyStore = new SenderKeyStore(getSharedSenderKeysFile(dataPath, account),
getSenderKeysPath(dataPath, account),
recipientStore::resolveRecipientAddress,
recipientStore);
signalProtocolStore = new SignalProtocolStore(preKeyStore,
signedPreKeyStore,
sessionStore,
identityKeyStore,
senderKeyStore,
this::isMultiDevice);
messageCache = new MessageCache(getMessageCachePath(dataPath, account));
}
public static SignalAccount createOrUpdateLinkedAccount(
File dataPath,
String account,
@ -240,9 +220,9 @@ public class SignalAccount implements Closeable {
final var signalAccount = load(dataPath, account, true, trustNewIdentity);
signalAccount.setProvisioningData(account, aci, password, encryptedDeviceName, deviceId, profileKey);
signalAccount.recipientStore.resolveRecipientTrusted(signalAccount.getSelfAddress());
signalAccount.sessionStore.archiveAllSessions();
signalAccount.senderKeyStore.deleteAll();
signalAccount.getRecipientStore().resolveRecipientTrusted(signalAccount.getSelfAddress());
signalAccount.getSessionStore().archiveAllSessions();
signalAccount.getSenderKeyStore().deleteAll();
signalAccount.clearAllPreKeys();
return signalAccount;
}
@ -250,8 +230,8 @@ public class SignalAccount implements Closeable {
private void clearAllPreKeys() {
this.preKeyIdOffset = new SecureRandom().nextInt(Medium.MAX_VALUE);
this.nextSignedPreKeyId = new SecureRandom().nextInt(Medium.MAX_VALUE);
this.preKeyStore.removeAllPreKeys();
this.signedPreKeyStore.removeAllSignedPreKeys();
this.getPreKeyStore().removeAllPreKeys();
this.getSignedPreKeyStore().removeAllSignedPreKeys();
save();
}
@ -275,14 +255,17 @@ public class SignalAccount implements Closeable {
signalAccount.setProvisioningData(account, aci, password, encryptedDeviceName, deviceId, profileKey);
signalAccount.initStores(dataPath, identityKey, registrationId, trustNewIdentity);
signalAccount.dataPath = dataPath;
signalAccount.identityKeyPair = identityKey;
signalAccount.localRegistrationId = registrationId;
signalAccount.trustNewIdentity = trustNewIdentity;
signalAccount.groupStore = new GroupStore(getGroupCachePath(dataPath, account),
signalAccount.recipientStore,
signalAccount.getRecipientStore(),
signalAccount::saveGroupStore);
signalAccount.stickerStore = new StickerStore(signalAccount::saveStickerStore);
signalAccount.configurationStore = new ConfigurationStore(signalAccount::saveConfigurationStore);
signalAccount.recipientStore.resolveRecipientTrusted(signalAccount.getSelfAddress());
signalAccount.getRecipientStore().resolveRecipientTrusted(signalAccount.getSelfAddress());
signalAccount.previousStorageVersion = CURRENT_STORAGE_VERSION;
signalAccount.migrateLegacyConfigs();
signalAccount.save();
@ -335,19 +318,19 @@ public class SignalAccount implements Closeable {
}
private void mergeRecipients(RecipientId recipientId, RecipientId toBeMergedRecipientId) {
sessionStore.mergeRecipients(recipientId, toBeMergedRecipientId);
identityKeyStore.mergeRecipients(recipientId, toBeMergedRecipientId);
messageCache.mergeRecipients(recipientId, toBeMergedRecipientId);
groupStore.mergeRecipients(recipientId, toBeMergedRecipientId);
senderKeyStore.mergeRecipients(recipientId, toBeMergedRecipientId);
getSessionStore().mergeRecipients(recipientId, toBeMergedRecipientId);
getIdentityKeyStore().mergeRecipients(recipientId, toBeMergedRecipientId);
getMessageCache().mergeRecipients(recipientId, toBeMergedRecipientId);
getGroupStore().mergeRecipients(recipientId, toBeMergedRecipientId);
getSenderKeyStore().mergeRecipients(recipientId, toBeMergedRecipientId);
}
public void removeRecipient(final RecipientId recipientId) {
sessionStore.deleteAllSessions(recipientId);
identityKeyStore.deleteIdentity(recipientId);
messageCache.deleteMessages(recipientId);
senderKeyStore.deleteAll(recipientId);
recipientStore.deleteRecipientData(recipientId);
getSessionStore().deleteAllSessions(recipientId);
getIdentityKeyStore().deleteIdentity(recipientId);
getMessageCache().deleteMessages(recipientId);
getSenderKeyStore().deleteAll(recipientId);
getRecipientStore().deleteRecipientData(recipientId);
}
public static File getFileName(File dataPath, String account) {
@ -505,7 +488,10 @@ public class SignalAccount implements Closeable {
migratedLegacyConfig = true;
}
initStores(dataPath, identityKeyPair, registrationId, trustNewIdentity);
this.dataPath = dataPath;
this.identityKeyPair = identityKeyPair;
this.localRegistrationId = registrationId;
this.trustNewIdentity = trustNewIdentity;
migratedLegacyConfig = loadLegacyStores(rootNode, legacySignalProtocolStore) || migratedLegacyConfig;
@ -513,10 +499,12 @@ public class SignalAccount implements Closeable {
groupStoreStorage = jsonProcessor.convertValue(rootNode.get("groupStore"), GroupStore.Storage.class);
groupStore = GroupStore.fromStorage(groupStoreStorage,
getGroupCachePath(dataPath, account),
recipientStore,
getRecipientStore(),
this::saveGroupStore);
} else {
groupStore = new GroupStore(getGroupCachePath(dataPath, account), recipientStore, this::saveGroupStore);
groupStore = new GroupStore(getGroupCachePath(dataPath, account),
getRecipientStore(),
this::saveGroupStore);
}
if (rootNode.hasNonNull("stickerStore")) {
@ -551,7 +539,7 @@ public class SignalAccount implements Closeable {
logger.debug("Migrating legacy recipient store.");
var legacyRecipientStore = jsonProcessor.convertValue(legacyRecipientStoreNode, LegacyRecipientStore.class);
if (legacyRecipientStore != null) {
recipientStore.resolveRecipientsTrusted(legacyRecipientStore.getAddresses());
getRecipientStore().resolveRecipientsTrusted(legacyRecipientStore.getAddresses());
}
getSelfRecipientId();
migrated = true;
@ -561,7 +549,7 @@ public class SignalAccount implements Closeable {
logger.debug("Migrating legacy pre key store.");
for (var entry : legacySignalProtocolStore.getLegacyPreKeyStore().getPreKeys().entrySet()) {
try {
preKeyStore.storePreKey(entry.getKey(), new PreKeyRecord(entry.getValue()));
getPreKeyStore().storePreKey(entry.getKey(), new PreKeyRecord(entry.getValue()));
} catch (IOException e) {
logger.warn("Failed to migrate pre key, ignoring", e);
}
@ -573,7 +561,7 @@ public class SignalAccount implements Closeable {
logger.debug("Migrating legacy signed pre key store.");
for (var entry : legacySignalProtocolStore.getLegacySignedPreKeyStore().getSignedPreKeys().entrySet()) {
try {
signedPreKeyStore.storeSignedPreKey(entry.getKey(), new SignedPreKeyRecord(entry.getValue()));
getSignedPreKeyStore().storeSignedPreKey(entry.getKey(), new SignedPreKeyRecord(entry.getValue()));
} catch (IOException e) {
logger.warn("Failed to migrate signed pre key, ignoring", e);
}
@ -585,7 +573,7 @@ public class SignalAccount implements Closeable {
logger.debug("Migrating legacy session store.");
for (var session : legacySignalProtocolStore.getLegacySessionStore().getSessions()) {
try {
sessionStore.storeSession(new SignalProtocolAddress(session.address.getIdentifier(),
getSessionStore().storeSession(new SignalProtocolAddress(session.address.getIdentifier(),
session.deviceId), new SessionRecord(session.sessionRecord));
} catch (Exception e) {
logger.warn("Failed to migrate session, ignoring", e);
@ -597,9 +585,9 @@ public class SignalAccount implements Closeable {
if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacyIdentityKeyStore() != null) {
logger.debug("Migrating legacy identity session store.");
for (var identity : legacySignalProtocolStore.getLegacyIdentityKeyStore().getIdentities()) {
RecipientId recipientId = recipientStore.resolveRecipientTrusted(identity.getAddress());
identityKeyStore.saveIdentity(recipientId, identity.getIdentityKey(), identity.getDateAdded());
identityKeyStore.setIdentityTrustLevel(recipientId,
RecipientId recipientId = getRecipientStore().resolveRecipientTrusted(identity.getAddress());
getIdentityKeyStore().saveIdentity(recipientId, identity.getIdentityKey(), identity.getDateAdded());
getIdentityKeyStore().setIdentityTrustLevel(recipientId,
identity.getIdentityKey(),
identity.getTrustLevel());
}
@ -611,8 +599,8 @@ public class SignalAccount implements Closeable {
final var contactStoreNode = rootNode.get("contactStore");
final var contactStore = jsonProcessor.convertValue(contactStoreNode, LegacyJsonContactsStore.class);
for (var contact : contactStore.getContacts()) {
final var recipientId = recipientStore.resolveRecipientTrusted(contact.getAddress());
recipientStore.storeContact(recipientId,
final var recipientId = getRecipientStore().resolveRecipientTrusted(contact.getAddress());
getRecipientStore().storeContact(recipientId,
new Contact(contact.name,
contact.color,
contact.messageExpirationTime,
@ -639,9 +627,9 @@ public class SignalAccount implements Closeable {
var profileStoreNode = rootNode.get("profileStore");
final var legacyProfileStore = jsonProcessor.convertValue(profileStoreNode, LegacyProfileStore.class);
for (var profileEntry : legacyProfileStore.getProfileEntries()) {
var recipientId = recipientStore.resolveRecipient(profileEntry.getAddress());
recipientStore.storeProfileKeyCredential(recipientId, profileEntry.getProfileKeyCredential());
recipientStore.storeProfileKey(recipientId, profileEntry.getProfileKey());
var recipientId = getRecipientStore().resolveRecipient(profileEntry.getAddress());
getRecipientStore().storeProfileKeyCredential(recipientId, profileEntry.getProfileKeyCredential());
getRecipientStore().storeProfileKey(recipientId, profileEntry.getProfileKey());
final var profile = profileEntry.getProfile();
if (profile != null) {
final var capabilities = new HashSet<Profile.Capability>();
@ -668,7 +656,7 @@ public class SignalAccount implements Closeable {
? Profile.UnidentifiedAccessMode.ENABLED
: Profile.UnidentifiedAccessMode.DISABLED,
capabilities);
recipientStore.storeProfile(recipientId, newProfile);
getRecipientStore().storeProfile(recipientId, newProfile);
}
}
}
@ -687,10 +675,10 @@ public class SignalAccount implements Closeable {
}
try {
if (UuidUtil.isUuid(thread.id) || thread.id.startsWith("+")) {
final var recipientId = recipientStore.resolveRecipient(thread.id);
var contact = recipientStore.getContact(recipientId);
final var recipientId = getRecipientStore().resolveRecipient(thread.id);
var contact = getRecipientStore().getContact(recipientId);
if (contact != null) {
recipientStore.storeContact(recipientId,
getRecipientStore().storeContact(recipientId,
Contact.newBuilder(contact)
.withMessageExpirationTime(thread.messageExpirationTime)
.build());
@ -738,13 +726,10 @@ public class SignalAccount implements Closeable {
.put("isMultiDevice", isMultiDevice)
.put("lastReceiveTimestamp", lastReceiveTimestamp)
.put("password", password)
.put("registrationId", identityKeyStore.getLocalRegistrationId())
.put("registrationId", localRegistrationId)
.put("identityPrivateKey",
Base64.getEncoder()
.encodeToString(identityKeyStore.getIdentityKeyPair().getPrivateKey().serialize()))
.put("identityKey",
Base64.getEncoder()
.encodeToString(identityKeyStore.getIdentityKeyPair().getPublicKey().serialize()))
Base64.getEncoder().encodeToString(identityKeyPair.getPrivateKey().serialize()))
.put("identityKey", Base64.getEncoder().encodeToString(identityKeyPair.getPublicKey().serialize()))
.put("registrationLockPin", registrationLockPin)
.put("pinMasterKey",
pinMasterKey == null ? null : Base64.getEncoder().encodeToString(pinMasterKey.serialize()))
@ -796,7 +781,7 @@ public class SignalAccount implements Closeable {
logger.error("Invalid pre key id {}, expected {}", record.getId(), preKeyIdOffset);
throw new AssertionError("Invalid pre key id");
}
preKeyStore.storePreKey(record.getId(), record);
getPreKeyStore().storePreKey(record.getId(), record);
preKeyIdOffset = (preKeyIdOffset + 1) % Medium.MAX_VALUE;
}
save();
@ -807,21 +792,42 @@ public class SignalAccount implements Closeable {
logger.error("Invalid signed pre key id {}, expected {}", record.getId(), nextSignedPreKeyId);
throw new AssertionError("Invalid signed pre key id");
}
signalProtocolStore.storeSignedPreKey(record.getId(), record);
getSignedPreKeyStore().storeSignedPreKey(record.getId(), record);
nextSignedPreKeyId = (nextSignedPreKeyId + 1) % Medium.MAX_VALUE;
save();
}
public SignalProtocolStore getSignalProtocolStore() {
return signalProtocolStore;
return getOrCreate(() -> signalProtocolStore,
() -> signalProtocolStore = new SignalProtocolStore(getPreKeyStore(),
getSignedPreKeyStore(),
getSessionStore(),
getIdentityKeyStore(),
getSenderKeyStore(),
this::isMultiDevice));
}
private PreKeyStore getPreKeyStore() {
return getOrCreate(() -> preKeyStore, () -> preKeyStore = new PreKeyStore(getPreKeysPath(dataPath, account)));
}
private SignedPreKeyStore getSignedPreKeyStore() {
return getOrCreate(() -> signedPreKeyStore,
() -> signedPreKeyStore = new SignedPreKeyStore(getSignedPreKeysPath(dataPath, account)));
}
public SessionStore getSessionStore() {
return sessionStore;
return getOrCreate(() -> sessionStore,
() -> sessionStore = new SessionStore(getSessionsPath(dataPath, account), getRecipientStore()));
}
public IdentityKeyStore getIdentityKeyStore() {
return identityKeyStore;
return getOrCreate(() -> identityKeyStore,
() -> identityKeyStore = new IdentityKeyStore(getIdentitiesPath(dataPath, account),
getRecipientStore(),
identityKeyPair,
localRegistrationId,
trustNewIdentity));
}
public GroupStore getGroupStore() {
@ -829,15 +835,17 @@ public class SignalAccount implements Closeable {
}
public ContactsStore getContactStore() {
return recipientStore;
return getRecipientStore();
}
public RecipientStore getRecipientStore() {
return recipientStore;
return getOrCreate(() -> recipientStore,
() -> recipientStore = RecipientStore.load(getRecipientsStoreFile(dataPath, account),
this::mergeRecipients));
}
public ProfileStore getProfileStore() {
return recipientStore;
return getRecipientStore();
}
public StickerStore getStickerStore() {
@ -845,7 +853,11 @@ public class SignalAccount implements Closeable {
}
public SenderKeyStore getSenderKeyStore() {
return senderKeyStore;
return getOrCreate(() -> senderKeyStore,
() -> senderKeyStore = new SenderKeyStore(getSharedSenderKeysFile(dataPath, account),
getSenderKeysPath(dataPath, account),
getRecipientStore()::resolveRecipientAddress,
getRecipientStore()));
}
public ConfigurationStore getConfigurationStore() {
@ -853,7 +865,8 @@ public class SignalAccount implements Closeable {
}
public MessageCache getMessageCache() {
return messageCache;
return getOrCreate(() -> messageCache,
() -> messageCache = new MessageCache(getMessageCachePath(dataPath, account)));
}
public String getAccount() {
@ -874,7 +887,8 @@ public class SignalAccount implements Closeable {
}
public RecipientId getSelfRecipientId() {
return recipientStore.resolveRecipientTrusted(new RecipientAddress(aci == null ? null : aci.uuid(), account));
return getRecipientStore().resolveRecipientTrusted(new RecipientAddress(aci == null ? null : aci.uuid(),
account));
}
public String getEncryptedDeviceName() {
@ -895,11 +909,11 @@ public class SignalAccount implements Closeable {
}
public IdentityKeyPair getIdentityKeyPair() {
return signalProtocolStore.getIdentityKeyPair();
return identityKeyPair;
}
public int getLocalRegistrationId() {
return signalProtocolStore.getLocalRegistrationId();
return localRegistrationId;
}
public String getPassword() {
@ -1026,7 +1040,7 @@ public class SignalAccount implements Closeable {
clearAllPreKeys();
getSessionStore().archiveAllSessions();
senderKeyStore.deleteAll();
getSenderKeyStore().deleteAll();
final var recipientId = getRecipientStore().resolveRecipientTrusted(getSelfAddress());
final var publicKey = getIdentityKeyPair().getPublicKey();
getIdentityKeyStore().saveIdentity(recipientId, publicKey, new Date());
@ -1047,4 +1061,25 @@ public class SignalAccount implements Closeable {
}
}
}
private <T> T getOrCreate(Supplier<T> supplier, Callable creator) {
var value = supplier.get();
if (value != null) {
return value;
}
synchronized (LOCK) {
value = supplier.get();
if (value != null) {
return value;
}
creator.call();
return supplier.get();
}
}
private interface Callable {
void call();
}
}

View file

@ -49,7 +49,7 @@ public class RecipientStore implements RecipientResolver, ContactsStore, Profile
private long lastId;
private boolean isBulkUpdating;
public static RecipientStore load(File file, RecipientMergeHandler recipientMergeHandler) throws IOException {
public static RecipientStore load(File file, RecipientMergeHandler recipientMergeHandler) {
final var objectMapper = Utils.createStorageObjectMapper();
try (var inputStream = new FileInputStream(file)) {
final var storage = objectMapper.readValue(inputStream, Storage.class);
@ -114,6 +114,9 @@ public class RecipientStore implements RecipientResolver, ContactsStore, Profile
} catch (FileNotFoundException e) {
logger.trace("Creating new recipient store.");
return new RecipientStore(objectMapper, file, recipientMergeHandler, new HashMap<>(), 0);
} catch (IOException e) {
logger.warn("Failed to load recipient store", e);
throw new RuntimeException(e);
}
}

View file

@ -42,7 +42,7 @@ public class SenderKeySharedStore {
public static SenderKeySharedStore load(
final File file, final RecipientAddressResolver addressResolver, final RecipientResolver resolver
) throws IOException {
) {
final var objectMapper = Utils.createStorageObjectMapper();
try (var inputStream = new FileInputStream(file)) {
final var storage = objectMapper.readValue(inputStream, Storage.class);
@ -70,6 +70,9 @@ public class SenderKeySharedStore {
} catch (FileNotFoundException e) {
logger.trace("Creating new shared sender key store.");
return new SenderKeySharedStore(new HashMap<>(), objectMapper, file, addressResolver, resolver);
} catch (IOException e) {
logger.warn("Failed to load shared sender key store", e);
throw new RuntimeException(e);
}
}

View file

@ -9,7 +9,6 @@ import org.whispersystems.signalservice.api.SignalServiceSenderKeyStore;
import org.whispersystems.signalservice.api.push.DistributionId;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Set;
import java.util.UUID;
@ -24,7 +23,7 @@ public class SenderKeyStore implements SignalServiceSenderKeyStore {
final File senderKeysPath,
final RecipientAddressResolver addressResolver,
final RecipientResolver resolver
) throws IOException {
) {
this.senderKeyRecordStore = new SenderKeyRecordStore(senderKeysPath, resolver);
this.senderKeySharedStore = SenderKeySharedStore.load(file, addressResolver, resolver);
}