X-Git-Url: https://git.nmode.ca/signal-cli/blobdiff_plain/b62694dbc75c2b52ce5fc946805f61f244f43dc0..b94c1e50e62946a4d774a4c53ce70858145a4422:/src/main/java/org/asamk/signal/storage/SignalAccount.java diff --git a/src/main/java/org/asamk/signal/storage/SignalAccount.java b/src/main/java/org/asamk/signal/storage/SignalAccount.java index 6d83f78f..d9b37825 100644 --- a/src/main/java/org/asamk/signal/storage/SignalAccount.java +++ b/src/main/java/org/asamk/signal/storage/SignalAccount.java @@ -14,7 +14,12 @@ import org.asamk.signal.storage.contacts.ContactInfo; import org.asamk.signal.storage.contacts.JsonContactsStore; import org.asamk.signal.storage.groups.GroupInfo; import org.asamk.signal.storage.groups.JsonGroupStore; +import org.asamk.signal.storage.profiles.ProfileStore; +import org.asamk.signal.storage.protocol.JsonIdentityKeyStore; import org.asamk.signal.storage.protocol.JsonSignalProtocolStore; +import org.asamk.signal.storage.protocol.RecipientStore; +import org.asamk.signal.storage.protocol.SessionInfo; +import org.asamk.signal.storage.protocol.SignalServiceAddressResolver; import org.asamk.signal.storage.threads.LegacyJsonThreadStore; import org.asamk.signal.storage.threads.ThreadInfo; import org.asamk.signal.util.IOUtils; @@ -25,23 +30,27 @@ import org.whispersystems.libsignal.IdentityKeyPair; import org.whispersystems.libsignal.state.PreKeyRecord; import org.whispersystems.libsignal.state.SignedPreKeyRecord; import org.whispersystems.libsignal.util.Medium; +import org.whispersystems.libsignal.util.Pair; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.util.Base64; +import java.io.Closeable; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.channels.Channels; +import java.nio.channels.ClosedChannelException; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.util.Collection; import java.util.UUID; +import java.util.stream.Collectors; -public class SignalAccount { +public class SignalAccount implements Closeable { private final ObjectMapper jsonProcessor = new ObjectMapper(); - private FileChannel fileChannel; - private FileLock lock; + private final FileChannel fileChannel; + private final FileLock lock; private String username; private UUID uuid; private int deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID; @@ -58,8 +67,12 @@ public class SignalAccount { private JsonSignalProtocolStore signalProtocolStore; private JsonGroupStore groupStore; private JsonContactsStore contactStore; + private RecipientStore recipientStore; + private ProfileStore profileStore; - private SignalAccount() { + private SignalAccount(final FileChannel fileChannel, final FileLock lock) { + this.fileChannel = fileChannel; + this.lock = lock; jsonProcessor.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); // disable autodetect jsonProcessor.enable(SerializationFeature.INDENT_OUTPUT); // for pretty print, you can disable it. jsonProcessor.enable(SerializationFeature.WRITE_NULL_MAP_VALUES); @@ -69,36 +82,53 @@ public class SignalAccount { } public static SignalAccount load(String dataPath, String username) throws IOException { - SignalAccount account = new SignalAccount(); - IOUtils.createPrivateDirectories(dataPath); - account.openFileChannel(getFileName(dataPath, username)); - account.load(); - return account; + final String fileName = getFileName(dataPath, username); + final Pair pair = openFileChannel(fileName); + try { + SignalAccount account = new SignalAccount(pair.first(), pair.second()); + account.load(); + return account; + } catch (Throwable e) { + pair.second().close(); + pair.first().close(); + throw e; + } } public static SignalAccount create(String dataPath, String username, IdentityKeyPair identityKey, int registrationId, ProfileKey profileKey) throws IOException { IOUtils.createPrivateDirectories(dataPath); + String fileName = getFileName(dataPath, username); + if (!new File(fileName).exists()) { + IOUtils.createPrivateFile(fileName); + } - SignalAccount account = new SignalAccount(); - account.openFileChannel(getFileName(dataPath, username)); + final Pair pair = openFileChannel(fileName); + SignalAccount account = new SignalAccount(pair.first(), pair.second()); account.username = username; account.profileKey = profileKey; account.signalProtocolStore = new JsonSignalProtocolStore(identityKey, registrationId); account.groupStore = new JsonGroupStore(); account.contactStore = new JsonContactsStore(); + account.recipientStore = new RecipientStore(); + account.profileStore = new ProfileStore(); account.registered = false; return account; } - public static SignalAccount createLinkedAccount(String dataPath, String username, String password, int deviceId, IdentityKeyPair identityKey, int registrationId, String signalingKey, ProfileKey profileKey) throws IOException { + public static SignalAccount createLinkedAccount(String dataPath, String username, UUID uuid, String password, int deviceId, IdentityKeyPair identityKey, int registrationId, String signalingKey, ProfileKey profileKey) throws IOException { IOUtils.createPrivateDirectories(dataPath); + String fileName = getFileName(dataPath, username); + if (!new File(fileName).exists()) { + IOUtils.createPrivateFile(fileName); + } - SignalAccount account = new SignalAccount(); - account.openFileChannel(getFileName(dataPath, username)); + final Pair pair = openFileChannel(fileName); + SignalAccount account = new SignalAccount(pair.first(), pair.second()); account.username = username; + account.uuid = uuid; account.password = password; account.profileKey = profileKey; account.deviceId = deviceId; @@ -106,21 +136,14 @@ public class SignalAccount { account.signalProtocolStore = new JsonSignalProtocolStore(identityKey, registrationId); account.groupStore = new JsonGroupStore(); account.contactStore = new JsonContactsStore(); + account.recipientStore = new RecipientStore(); + account.profileStore = new ProfileStore(); account.registered = true; account.isMultiDevice = true; return account; } - public static SignalAccount createTemporaryAccount(IdentityKeyPair identityKey, int registrationId) { - SignalAccount account = new SignalAccount(); - - account.signalProtocolStore = new JsonSignalProtocolStore(identityKey, registrationId); - account.registered = false; - - return account; - } - public static String getFileName(String dataPath, String username) { return dataPath + "/" + username; } @@ -140,6 +163,14 @@ public class SignalAccount { rootNode = jsonProcessor.readTree(Channels.newInputStream(fileChannel)); } + JsonNode uuidNode = rootNode.get("uuid"); + if (uuidNode != null && !uuidNode.isNull()) { + try { + uuid = UUID.fromString(uuidNode.asText()); + } catch (IllegalArgumentException e) { + throw new IOException("Config file contains an invalid uuid, needs to be a valid UUID", e); + } + } JsonNode node = rootNode.get("deviceId"); if (node != null) { deviceId = node.asInt(); @@ -189,11 +220,51 @@ public class SignalAccount { if (contactStore == null) { contactStore = new JsonContactsStore(); } + + JsonNode recipientStoreNode = rootNode.get("recipientStore"); + if (recipientStoreNode != null) { + recipientStore = jsonProcessor.convertValue(recipientStoreNode, RecipientStore.class); + } + if (recipientStore == null) { + recipientStore = new RecipientStore(); + + recipientStore.resolveServiceAddress(getSelfAddress()); + + for (ContactInfo contact : contactStore.getContacts()) { + recipientStore.resolveServiceAddress(contact.getAddress()); + } + + for (GroupInfo group : groupStore.getGroups()) { + group.members = group.members.stream() + .map(m -> recipientStore.resolveServiceAddress(m)) + .collect(Collectors.toSet()); + } + + for (SessionInfo session : signalProtocolStore.getSessions()) { + session.address = recipientStore.resolveServiceAddress(session.address); + } + + for (JsonIdentityKeyStore.Identity identity : signalProtocolStore.getIdentities()) { + identity.setAddress(recipientStore.resolveServiceAddress(identity.getAddress())); + } + } + + JsonNode profileStoreNode = rootNode.get("profileStore"); + if (profileStoreNode != null) { + profileStore = jsonProcessor.convertValue(profileStoreNode, ProfileStore.class); + } + if (profileStore == null) { + profileStore = new ProfileStore(); + } + JsonNode threadStoreNode = rootNode.get("threadStore"); if (threadStoreNode != null) { LegacyJsonThreadStore threadStore = jsonProcessor.convertValue(threadStoreNode, LegacyJsonThreadStore.class); // Migrate thread info to group and contact store for (ThreadInfo thread : threadStore.getThreads()) { + if (thread.id == null || thread.id.isEmpty()) { + continue; + } try { ContactInfo contactInfo = contactStore.getContact(new SignalServiceAddress(null, thread.id)); if (contactInfo != null) { @@ -218,6 +289,7 @@ public class SignalAccount { } ObjectNode rootNode = jsonProcessor.createObjectNode(); rootNode.put("username", username) + .put("uuid", uuid == null ? null : uuid.toString()) .put("deviceId", deviceId) .put("isMultiDevice", isMultiDevice) .put("password", password) @@ -230,6 +302,8 @@ public class SignalAccount { .putPOJO("axolotlStore", signalProtocolStore) .putPOJO("groupStore", groupStore) .putPOJO("contactStore", contactStore) + .putPOJO("recipientStore", recipientStore) + .putPOJO("profileStore", profileStore) ; try { synchronized (fileChannel) { @@ -243,21 +317,19 @@ public class SignalAccount { } } - private void openFileChannel(String fileName) throws IOException { - if (fileChannel != null) { - return; - } - - if (!new File(fileName).exists()) { - IOUtils.createPrivateFile(fileName); - } - fileChannel = new RandomAccessFile(new File(fileName), "rw").getChannel(); - lock = fileChannel.tryLock(); + private static Pair openFileChannel(String fileName) throws IOException { + FileChannel fileChannel = new RandomAccessFile(new File(fileName), "rw").getChannel(); + FileLock lock = fileChannel.tryLock(); if (lock == null) { System.err.println("Config file is in use by another instance, waiting…"); lock = fileChannel.lock(); System.err.println("Config file lock acquired."); } + return new Pair<>(fileChannel, lock); + } + + public void setResolver(final SignalServiceAddressResolver resolver) { + signalProtocolStore.setResolver(resolver); } public void addPreKeys(Collection records) { @@ -284,6 +356,14 @@ public class SignalAccount { return contactStore; } + public RecipientStore getRecipientStore() { + return recipientStore; + } + + public ProfileStore getProfileStore() { + return profileStore; + } + public String getUsername() { return username; } @@ -292,6 +372,10 @@ public class SignalAccount { return uuid; } + public void setUuid(final UUID uuid) { + this.uuid = uuid; + } + public SignalServiceAddress getSelfAddress() { return new SignalServiceAddress(uuid, username); } @@ -359,4 +443,15 @@ public class SignalAccount { public void setMultiDevice(final boolean multiDevice) { isMultiDevice = multiDevice; } + + @Override + public void close() throws IOException { + synchronized (fileChannel) { + try { + lock.close(); + } catch (ClosedChannelException ignored) { + } + fileChannel.close(); + } + } }