Extract SignalAccount from Manager

This commit is contained in:
AsamK 2018-11-18 15:34:10 +01:00
parent 701328b8c2
commit 35c72f692f
30 changed files with 793 additions and 549 deletions

View file

@ -2,10 +2,18 @@
<code_scheme name="Project" version="173"> <code_scheme name="Project" version="173">
<option name="LINE_SEPARATOR" value="&#10;" /> <option name="LINE_SEPARATOR" value="&#10;" />
<JavaCodeStyleSettings> <JavaCodeStyleSettings>
<option name="GENERATE_FINAL_LOCALS" value="true" />
<option name="GENERATE_FINAL_PARAMETERS" value="true" />
<option name="JD_P_AT_EMPTY_LINES" value="false" /> <option name="JD_P_AT_EMPTY_LINES" value="false" />
</JavaCodeStyleSettings> </JavaCodeStyleSettings>
<XML> <XML>
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" /> <option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
</XML> </XML>
<codeStyleSettings language="JAVA">
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="1" />
<option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" />
</codeStyleSettings>
</code_scheme> </code_scheme>
</component> </component>

View file

@ -20,7 +20,7 @@ repositories {
} }
dependencies { dependencies {
compile 'com.github.turasa:signal-service-java:2.12.2_unofficial_1' compile 'com.github.turasa:signal-service-java:2.12.2_unofficial_2'
compile 'org.bouncycastle:bcprov-jdk15on:1.60' compile 'org.bouncycastle:bcprov-jdk15on:1.60'
compile 'net.sourceforge.argparse4j:argparse4j:0.8.1' compile 'net.sourceforge.argparse4j:argparse4j:0.8.1'
compile 'org.freedesktop.dbus:dbus-java:2.7.0' compile 'org.freedesktop.dbus:dbus-java:2.7.0'

View file

@ -11,6 +11,7 @@ import java.io.IOException;
import java.util.List; import java.util.List;
public interface Signal extends DBusInterface { public interface Signal extends DBusInterface {
void sendMessage(String message, List<String> attachments, String recipient) throws EncapsulatedExceptions, AttachmentInvalidException, IOException; void sendMessage(String message, List<String> attachments, String recipient) throws EncapsulatedExceptions, AttachmentInvalidException, IOException;
void sendMessage(String message, List<String> attachments, List<String> recipients) throws EncapsulatedExceptions, AttachmentInvalidException, IOException; void sendMessage(String message, List<String> attachments, List<String> recipients) throws EncapsulatedExceptions, AttachmentInvalidException, IOException;
@ -32,6 +33,7 @@ public interface Signal extends DBusInterface {
byte[] updateGroup(byte[] groupId, String name, List<String> members, String avatar) throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException; byte[] updateGroup(byte[] groupId, String name, List<String> members, String avatar) throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException;
class MessageReceived extends DBusSignal { class MessageReceived extends DBusSignal {
private long timestamp; private long timestamp;
private String sender; private String sender;
private byte[] groupId; private byte[] groupId;
@ -69,6 +71,7 @@ public interface Signal extends DBusInterface {
} }
class ReceiptReceived extends DBusSignal { class ReceiptReceived extends DBusSignal {
private long timestamp; private long timestamp;
private String sender; private String sender;

View file

@ -3,6 +3,7 @@ package org.asamk.signal;
import org.freedesktop.dbus.exceptions.DBusExecutionException; import org.freedesktop.dbus.exceptions.DBusExecutionException;
public class AttachmentInvalidException extends DBusExecutionException { public class AttachmentInvalidException extends DBusExecutionException {
public AttachmentInvalidException(String message) { public AttachmentInvalidException(String message) {
super(message); super(message);
} }

View file

@ -4,6 +4,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
class JsonAttachment { class JsonAttachment {
String contentType; String contentType;
long id; long id;
int size; int size;

View file

@ -5,6 +5,7 @@ import org.whispersystems.signalservice.api.messages.calls.*;
import java.util.List; import java.util.List;
class JsonCallMessage { class JsonCallMessage {
OfferMessage offerMessage; OfferMessage offerMessage;
AnswerMessage answerMessage; AnswerMessage answerMessage;
BusyMessage busyMessage; BusyMessage busyMessage;

View file

@ -7,6 +7,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
class JsonDataMessage { class JsonDataMessage {
long timestamp; long timestamp;
String message; String message;
int expiresInSeconds; int expiresInSeconds;

View file

@ -1,6 +1,7 @@
package org.asamk.signal; package org.asamk.signal;
class JsonError { class JsonError {
String message; String message;
JsonError(Throwable exception) { JsonError(Throwable exception) {

View file

@ -6,6 +6,7 @@ import org.whispersystems.signalservice.internal.util.Base64;
import java.util.List; import java.util.List;
class JsonGroupInfo { class JsonGroupInfo {
String groupId; String groupId;
List<String> members; List<String> members;
String name; String name;

View file

@ -5,6 +5,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.SignalServiceAddress;
class JsonMessageEnvelope { class JsonMessageEnvelope {
String source; String source;
int sourceDevice; int sourceDevice;
String relay; String relay;

View file

@ -6,6 +6,7 @@ import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSy
import java.util.List; import java.util.List;
class JsonSyncMessage { class JsonSyncMessage {
JsonDataMessage sentMessage; JsonDataMessage sentMessage;
List<String> blockedNumbers; List<String> blockedNumbers;
List<ReadMessage> readMessages; List<ReadMessage> readMessages;

View file

@ -129,15 +129,13 @@ public class Main {
m = new Manager(username, settingsPath); m = new Manager(username, settingsPath);
ts = m; ts = m;
if (m.userExists()) {
try { try {
m.init(); m.init();
} catch (Exception e) { } catch (Exception e) {
System.err.println("Error loading state file \"" + m.getFileName() + "\": " + e.getMessage()); System.err.println("Error loading state file: " + e.getMessage());
return 2; return 2;
} }
} }
}
switch (ns.getString("command")) { switch (ns.getString("command")) {
case "register": case "register":
@ -145,9 +143,6 @@ public class Main {
System.err.println("register is not yet implemented via dbus"); System.err.println("register is not yet implemented via dbus");
return 1; return 1;
} }
if (!m.userHasKeys()) {
m.createNewIdentity();
}
try { try {
m.register(ns.getBoolean("voice")); m.register(ns.getBoolean("voice"));
} catch (IOException e) { } catch (IOException e) {
@ -252,9 +247,6 @@ public class Main {
return 1; return 1;
} }
// When linking, username is null and we always have to create keys
m.createNewIdentity();
String deviceName = ns.getString("name"); String deviceName = ns.getString("name");
if (deviceName == null) { if (deviceName == null) {
deviceName = "cli"; deviceName = "cli";
@ -736,7 +728,6 @@ public class Main {
System.err.println("Aborting sending."); System.err.println("Aborting sending.");
} }
private static void handleDBusExecutionException(DBusExecutionException e) { private static void handleDBusExecutionException(DBusExecutionException e) {
System.err.println("Cannot connect to dbus: " + e.getMessage()); System.err.println("Cannot connect to dbus: " + e.getMessage());
System.err.println("Aborting."); System.err.println("Aborting.");
@ -949,6 +940,7 @@ public class Main {
} }
private static class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { private static class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
final Manager m; final Manager m;
public ReceiveMessageHandler(Manager m) { public ReceiveMessageHandler(Manager m) {
@ -1212,6 +1204,7 @@ public class Main {
} }
private static class DbusReceiveMessageHandler extends ReceiveMessageHandler { private static class DbusReceiveMessageHandler extends ReceiveMessageHandler {
final DBusConnection conn; final DBusConnection conn;
public DbusReceiveMessageHandler(Manager m, DBusConnection conn) { public DbusReceiveMessageHandler(Manager m, DBusConnection conn) {
@ -1228,6 +1221,7 @@ public class Main {
} }
private static class JsonReceiveMessageHandler implements Manager.ReceiveMessageHandler { private static class JsonReceiveMessageHandler implements Manager.ReceiveMessageHandler {
final Manager m; final Manager m;
final ObjectMapper jsonProcessor; final ObjectMapper jsonProcessor;
@ -1259,6 +1253,7 @@ public class Main {
} }
private static class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler { private static class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler {
final DBusConnection conn; final DBusConnection conn;
public JsonDbusReceiveMessageHandler(Manager m, DBusConnection conn) { public JsonDbusReceiveMessageHandler(Manager m, DBusConnection conn) {
@ -1266,13 +1261,6 @@ public class Main {
this.conn = conn; this.conn = conn;
} }
@Override
public void handleMessage(SignalServiceEnvelope envelope, SignalServiceContent content, Throwable exception) {
super.handleMessage(envelope, content, exception);
sendReceivedMessageToDbus(envelope, content, conn, m);
}
private static void sendReceivedMessageToDbus(SignalServiceEnvelope envelope, SignalServiceContent content, DBusConnection conn, Manager m) { private static void sendReceivedMessageToDbus(SignalServiceEnvelope envelope, SignalServiceContent content, DBusConnection conn, Manager m) {
if (envelope.isReceipt()) { if (envelope.isReceipt()) {
try { try {
@ -1313,5 +1301,12 @@ public class Main {
} }
} }
} }
@Override
public void handleMessage(SignalServiceEnvelope envelope, SignalServiceContent content, Throwable exception) {
super.handleMessage(envelope, content, exception);
sendReceivedMessageToDbus(envelope, content, conn, m);
}
} }
} }

View file

@ -1,6 +1,7 @@
package org.asamk.signal; package org.asamk.signal;
public class UserAlreadyExists extends Exception { public class UserAlreadyExists extends Exception {
private String username; private String username;
private String fileName; private String fileName;

View file

@ -8,26 +8,25 @@ import org.whispersystems.signalservice.internal.configuration.SignalServiceUrl;
public class BaseConfig { public class BaseConfig {
private BaseConfig() {
}
public final static String PROJECT_NAME = Manager.class.getPackage().getImplementationTitle(); public final static String PROJECT_NAME = Manager.class.getPackage().getImplementationTitle();
public final static String PROJECT_VERSION = Manager.class.getPackage().getImplementationVersion(); public final static String PROJECT_VERSION = Manager.class.getPackage().getImplementationVersion();
final static String USER_AGENT = PROJECT_NAME == null ? null : PROJECT_NAME + " " + PROJECT_VERSION; final static String USER_AGENT = PROJECT_NAME == null ? null : PROJECT_NAME + " " + PROJECT_VERSION;
final static String UNIDENTIFIED_SENDER_TRUST_ROOT = "BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF";
final static int PREKEY_MINIMUM_COUNT = 20;
final static int PREKEY_BATCH_SIZE = 100;
final static int MAX_ATTACHMENT_SIZE = 150 * 1024 * 1024;
private final static String URL = "https://textsecure-service.whispersystems.org"; private final static String URL = "https://textsecure-service.whispersystems.org";
private final static String CDN_URL = "https://cdn.signal.org"; private final static String CDN_URL = "https://cdn.signal.org";
private final static TrustStore TRUST_STORE = new WhisperTrustStore(); private final static TrustStore TRUST_STORE = new WhisperTrustStore();
final static SignalServiceConfiguration serviceConfiguration = new SignalServiceConfiguration( final static SignalServiceConfiguration serviceConfiguration = new SignalServiceConfiguration(
new SignalServiceUrl[]{new SignalServiceUrl(URL, TRUST_STORE)}, new SignalServiceUrl[]{new SignalServiceUrl(URL, TRUST_STORE)},
new SignalCdnUrl[]{new SignalCdnUrl(CDN_URL, TRUST_STORE)}, new SignalCdnUrl[]{new SignalCdnUrl(CDN_URL, TRUST_STORE)},
new SignalContactDiscoveryUrl[0] new SignalContactDiscoveryUrl[0]
); );
final static String UNIDENTIFIED_SENDER_TRUST_ROOT = "BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF"; private BaseConfig() {
}
final static int PREKEY_MINIMUM_COUNT = 20;
static final int PREKEY_BATCH_SIZE = 100;
static final int MAX_ATTACHMENT_SIZE = 150 * 1024 * 1024;
} }

View file

@ -1,28 +1,28 @@
package org.asamk.signal.util; package org.asamk.signal.manager;
import org.whispersystems.signalservice.internal.util.Base64; import org.whispersystems.signalservice.internal.util.Base64;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom; import java.security.SecureRandom;
public class KeyUtils { class KeyUtils {
private KeyUtils() { private KeyUtils() {
} }
public static String createSignalingKey() { static String createSignalingKey() {
return getSecret(52); return getSecret(52);
} }
public static byte[] createProfileKey() { static byte[] createProfileKey() {
return getSecretBytes(32); return getSecretBytes(32);
} }
public static String createPassword() { static String createPassword() {
return getSecret(18); return getSecret(18);
} }
public static byte[] createGroupId() { static byte[] createGroupId() {
return getSecretBytes(16); return getSecretBytes(16);
} }

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,321 @@
package org.asamk.signal.storage;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.asamk.signal.storage.contacts.JsonContactsStore;
import org.asamk.signal.storage.groups.JsonGroupStore;
import org.asamk.signal.storage.protocol.JsonSignalProtocolStore;
import org.asamk.signal.storage.threads.JsonThreadStore;
import org.asamk.signal.util.IOUtils;
import org.asamk.signal.util.Util;
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.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.internal.util.Base64;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.Collection;
public class SignalAccount {
private final ObjectMapper jsonProcessor = new ObjectMapper();
private FileChannel fileChannel;
private FileLock lock;
private String username;
private int deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
private boolean isMultiDevice = false;
private String password;
private String registrationLockPin;
private String signalingKey;
private byte[] profileKey;
private int preKeyIdOffset;
private int nextSignedPreKeyId;
private boolean registered = false;
private JsonSignalProtocolStore signalProtocolStore;
private JsonGroupStore groupStore;
private JsonContactsStore contactStore;
private JsonThreadStore threadStore;
private SignalAccount() {
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);
jsonProcessor.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
jsonProcessor.disable(JsonParser.Feature.AUTO_CLOSE_SOURCE);
jsonProcessor.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET);
}
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;
}
public static SignalAccount create(String dataPath, String username, IdentityKeyPair identityKey, int registrationId, byte[] profileKey) throws IOException {
IOUtils.createPrivateDirectories(dataPath);
SignalAccount account = new SignalAccount();
account.openFileChannel(getFileName(dataPath, username));
account.username = username;
account.profileKey = profileKey;
account.signalProtocolStore = new JsonSignalProtocolStore(identityKey, registrationId);
account.groupStore = new JsonGroupStore();
account.threadStore = new JsonThreadStore();
account.contactStore = new JsonContactsStore();
account.registered = false;
return account;
}
public static SignalAccount createLinkedAccount(String dataPath, String username, String password, int deviceId, IdentityKeyPair identityKey, int registrationId, String signalingKey, byte[] profileKey) throws IOException {
IOUtils.createPrivateDirectories(dataPath);
SignalAccount account = new SignalAccount();
account.openFileChannel(getFileName(dataPath, username));
account.username = username;
account.password = password;
account.profileKey = profileKey;
account.deviceId = deviceId;
account.signalingKey = signalingKey;
account.signalProtocolStore = new JsonSignalProtocolStore(identityKey, registrationId);
account.groupStore = new JsonGroupStore();
account.threadStore = new JsonThreadStore();
account.contactStore = new JsonContactsStore();
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;
}
public static boolean userExists(String dataPath, String username) {
if (username == null) {
return false;
}
File f = new File(getFileName(dataPath, username));
return !(!f.exists() || f.isDirectory());
}
private void load() throws IOException {
JsonNode rootNode = jsonProcessor.readTree(Channels.newInputStream(fileChannel));
JsonNode node = rootNode.get("deviceId");
if (node != null) {
deviceId = node.asInt();
}
username = Util.getNotNullNode(rootNode, "username").asText();
password = Util.getNotNullNode(rootNode, "password").asText();
JsonNode pinNode = rootNode.get("registrationLockPin");
registrationLockPin = pinNode == null ? null : pinNode.asText();
if (rootNode.has("signalingKey")) {
signalingKey = Util.getNotNullNode(rootNode, "signalingKey").asText();
}
if (rootNode.has("preKeyIdOffset")) {
preKeyIdOffset = Util.getNotNullNode(rootNode, "preKeyIdOffset").asInt(0);
} else {
preKeyIdOffset = 0;
}
if (rootNode.has("nextSignedPreKeyId")) {
nextSignedPreKeyId = Util.getNotNullNode(rootNode, "nextSignedPreKeyId").asInt();
} else {
nextSignedPreKeyId = 0;
}
if (rootNode.has("profileKey")) {
profileKey = Base64.decode(Util.getNotNullNode(rootNode, "profileKey").asText());
}
signalProtocolStore = jsonProcessor.convertValue(Util.getNotNullNode(rootNode, "axolotlStore"), JsonSignalProtocolStore.class);
registered = Util.getNotNullNode(rootNode, "registered").asBoolean();
JsonNode groupStoreNode = rootNode.get("groupStore");
if (groupStoreNode != null) {
groupStore = jsonProcessor.convertValue(groupStoreNode, JsonGroupStore.class);
}
if (groupStore == null) {
groupStore = new JsonGroupStore();
}
JsonNode contactStoreNode = rootNode.get("contactStore");
if (contactStoreNode != null) {
contactStore = jsonProcessor.convertValue(contactStoreNode, JsonContactsStore.class);
}
if (contactStore == null) {
contactStore = new JsonContactsStore();
}
JsonNode threadStoreNode = rootNode.get("threadStore");
if (threadStoreNode != null) {
threadStore = jsonProcessor.convertValue(threadStoreNode, JsonThreadStore.class);
}
if (threadStore == null) {
threadStore = new JsonThreadStore();
}
}
public void save() {
if (fileChannel == null) {
return;
}
ObjectNode rootNode = jsonProcessor.createObjectNode();
rootNode.put("username", username)
.put("deviceId", deviceId)
.put("password", password)
.put("registrationLockPin", registrationLockPin)
.put("signalingKey", signalingKey)
.put("preKeyIdOffset", preKeyIdOffset)
.put("nextSignedPreKeyId", nextSignedPreKeyId)
.put("registered", registered)
.putPOJO("axolotlStore", signalProtocolStore)
.putPOJO("groupStore", groupStore)
.putPOJO("contactStore", contactStore)
.putPOJO("threadStore", threadStore)
;
try {
fileChannel.position(0);
jsonProcessor.writeValue(Channels.newOutputStream(fileChannel), rootNode);
fileChannel.truncate(fileChannel.position());
fileChannel.force(false);
} catch (Exception e) {
System.err.println(String.format("Error saving file: %s", e.getMessage()));
}
}
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();
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.");
}
}
public void addPreKeys(Collection<PreKeyRecord> records) {
for (PreKeyRecord record : records) {
signalProtocolStore.storePreKey(record.getId(), record);
}
preKeyIdOffset = (preKeyIdOffset + records.size()) % Medium.MAX_VALUE;
}
public void addSignedPreKey(SignedPreKeyRecord record) {
signalProtocolStore.storeSignedPreKey(record.getId(), record);
nextSignedPreKeyId = (nextSignedPreKeyId + 1) % Medium.MAX_VALUE;
}
public JsonSignalProtocolStore getSignalProtocolStore() {
return signalProtocolStore;
}
public JsonGroupStore getGroupStore() {
return groupStore;
}
public JsonContactsStore getContactStore() {
return contactStore;
}
public JsonThreadStore getThreadStore() {
return threadStore;
}
public String getUsername() {
return username;
}
public int getDeviceId() {
return deviceId;
}
public String getPassword() {
return password;
}
public void setPassword(final String password) {
this.password = password;
}
public String getRegistrationLockPin() {
return registrationLockPin;
}
public void setRegistrationLockPin(final String registrationLockPin) {
this.registrationLockPin = registrationLockPin;
}
public String getSignalingKey() {
return signalingKey;
}
public void setSignalingKey(final String signalingKey) {
this.signalingKey = signalingKey;
}
public byte[] getProfileKey() {
return profileKey;
}
public void setProfileKey(final byte[] profileKey) {
this.profileKey = profileKey;
}
public int getPreKeyIdOffset() {
return preKeyIdOffset;
}
public int getNextSignedPreKeyId() {
return nextSignedPreKeyId;
}
public boolean isRegistered() {
return registered;
}
public void setRegistered(final boolean registered) {
this.registered = registered;
}
public boolean isMultiDevice() {
return isMultiDevice;
}
public void setMultiDevice(final boolean multiDevice) {
isMultiDevice = multiDevice;
}
}

View file

@ -3,6 +3,7 @@ package org.asamk.signal.storage.contacts;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
public class ContactInfo { public class ContactInfo {
@JsonProperty @JsonProperty
public String name; public String name;

View file

@ -14,13 +14,13 @@ import java.util.List;
import java.util.Map; import java.util.Map;
public class JsonContactsStore { public class JsonContactsStore {
private static final ObjectMapper jsonProcessor = new ObjectMapper();
@JsonProperty("contacts") @JsonProperty("contacts")
@JsonSerialize(using = JsonContactsStore.MapToListSerializer.class) @JsonSerialize(using = JsonContactsStore.MapToListSerializer.class)
@JsonDeserialize(using = ContactsDeserializer.class) @JsonDeserialize(using = ContactsDeserializer.class)
private Map<String, ContactInfo> contacts = new HashMap<>(); private Map<String, ContactInfo> contacts = new HashMap<>();
private static final ObjectMapper jsonProcessor = new ObjectMapper();
public void updateContact(ContactInfo contact) { public void updateContact(ContactInfo contact) {
contacts.put(contact.number, contact); contacts.put(contact.number, contact);
} }
@ -41,6 +41,7 @@ public class JsonContactsStore {
} }
public static class MapToListSerializer extends JsonSerializer<Map<?, ?>> { public static class MapToListSerializer extends JsonSerializer<Map<?, ?>> {
@Override @Override
public void serialize(final Map<?, ?> value, final JsonGenerator jgen, final SerializerProvider provider) throws IOException { public void serialize(final Map<?, ?> value, final JsonGenerator jgen, final SerializerProvider provider) throws IOException {
jgen.writeObject(value.values()); jgen.writeObject(value.values());
@ -48,6 +49,7 @@ public class JsonContactsStore {
} }
public static class ContactsDeserializer extends JsonDeserializer<Map<String, ContactInfo>> { public static class ContactsDeserializer extends JsonDeserializer<Map<String, ContactInfo>> {
@Override @Override
public Map<String, ContactInfo> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { public Map<String, ContactInfo> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
Map<String, ContactInfo> contacts = new HashMap<>(); Map<String, ContactInfo> contacts = new HashMap<>();

View file

@ -8,6 +8,7 @@ import java.util.HashSet;
import java.util.Set; import java.util.Set;
public class GroupInfo { public class GroupInfo {
@JsonProperty @JsonProperty
public final byte[] groupId; public final byte[] groupId;
@ -16,20 +17,13 @@ public class GroupInfo {
@JsonProperty @JsonProperty
public Set<String> members = new HashSet<>(); public Set<String> members = new HashSet<>();
private long avatarId;
@JsonIgnore
public long getAvatarId() {
return avatarId;
}
@JsonProperty @JsonProperty
public boolean active; public boolean active;
@JsonProperty @JsonProperty
public String color; public String color;
private long avatarId;
public GroupInfo(byte[] groupId) { public GroupInfo(byte[] groupId) {
this.groupId = groupId; this.groupId = groupId;
} }
@ -41,4 +35,9 @@ public class GroupInfo {
this.avatarId = avatarId; this.avatarId = avatarId;
this.color = color; this.color = color;
} }
@JsonIgnore
public long getAvatarId() {
return avatarId;
}
} }

View file

@ -15,15 +15,16 @@ import java.util.List;
import java.util.Map; import java.util.Map;
public class JsonGroupStore { public class JsonGroupStore {
private static final ObjectMapper jsonProcessor = new ObjectMapper();
public static List<GroupInfo> groupsWithLegacyAvatarId = new ArrayList<>();
@JsonProperty("groups") @JsonProperty("groups")
@JsonSerialize(using = JsonGroupStore.MapToListSerializer.class) @JsonSerialize(using = JsonGroupStore.MapToListSerializer.class)
@JsonDeserialize(using = JsonGroupStore.GroupsDeserializer.class) @JsonDeserialize(using = JsonGroupStore.GroupsDeserializer.class)
private Map<String, GroupInfo> groups = new HashMap<>(); private Map<String, GroupInfo> groups = new HashMap<>();
public static List<GroupInfo> groupsWithLegacyAvatarId = new ArrayList<>();
private static final ObjectMapper jsonProcessor = new ObjectMapper();
public void updateGroup(GroupInfo group) { public void updateGroup(GroupInfo group) {
groups.put(Base64.encodeBytes(group.groupId), group); groups.put(Base64.encodeBytes(group.groupId), group);
} }
@ -38,6 +39,7 @@ public class JsonGroupStore {
} }
public static class MapToListSerializer extends JsonSerializer<Map<?, ?>> { public static class MapToListSerializer extends JsonSerializer<Map<?, ?>> {
@Override @Override
public void serialize(final Map<?, ?> value, final JsonGenerator jgen, final SerializerProvider provider) throws IOException { public void serialize(final Map<?, ?> value, final JsonGenerator jgen, final SerializerProvider provider) throws IOException {
jgen.writeObject(value.values()); jgen.writeObject(value.values());
@ -45,6 +47,7 @@ public class JsonGroupStore {
} }
public static class GroupsDeserializer extends JsonDeserializer<Map<String, GroupInfo>> { public static class GroupsDeserializer extends JsonDeserializer<Map<String, GroupInfo>> {
@Override @Override
public Map<String, GroupInfo> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { public Map<String, GroupInfo> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
Map<String, GroupInfo> groups = new HashMap<>(); Map<String, GroupInfo> groups = new HashMap<>();

View file

@ -22,7 +22,6 @@ public class JsonIdentityKeyStore implements IdentityKeyStore {
private final IdentityKeyPair identityKeyPair; private final IdentityKeyPair identityKeyPair;
private final int localRegistrationId; private final int localRegistrationId;
public JsonIdentityKeyStore(IdentityKeyPair identityKeyPair, int localRegistrationId) { public JsonIdentityKeyStore(IdentityKeyPair identityKeyPair, int localRegistrationId) {
this.identityKeyPair = identityKeyPair; this.identityKeyPair = identityKeyPair;
this.localRegistrationId = localRegistrationId; this.localRegistrationId = localRegistrationId;
@ -131,7 +130,6 @@ public class JsonIdentityKeyStore implements IdentityKeyStore {
int localRegistrationId = node.get("registrationId").asInt(); int localRegistrationId = node.get("registrationId").asInt();
IdentityKeyPair identityKeyPair = new IdentityKeyPair(Base64.decode(node.get("identityKey").asText())); IdentityKeyPair identityKeyPair = new IdentityKeyPair(Base64.decode(node.get("identityKey").asText()));
JsonIdentityKeyStore keyStore = new JsonIdentityKeyStore(identityKeyPair, localRegistrationId); JsonIdentityKeyStore keyStore = new JsonIdentityKeyStore(identityKeyPair, localRegistrationId);
JsonNode trustedKeysNode = node.get("trustedKeys"); JsonNode trustedKeysNode = node.get("trustedKeys");
@ -180,6 +178,7 @@ public class JsonIdentityKeyStore implements IdentityKeyStore {
} }
public class Identity { public class Identity {
IdentityKey identityKey; IdentityKey identityKey;
TrustLevel trustLevel; TrustLevel trustLevel;
Date added; Date added;

View file

@ -17,7 +17,6 @@ class JsonPreKeyStore implements PreKeyStore {
private final Map<Integer, byte[]> store = new HashMap<>(); private final Map<Integer, byte[]> store = new HashMap<>();
public JsonPreKeyStore() { public JsonPreKeyStore() {
} }
@ -60,7 +59,6 @@ class JsonPreKeyStore implements PreKeyStore {
public JsonPreKeyStore deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { public JsonPreKeyStore deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
JsonNode node = jsonParser.getCodec().readTree(jsonParser); JsonNode node = jsonParser.getCodec().readTree(jsonParser);
Map<Integer, byte[]> preKeyMap = new HashMap<>(); Map<Integer, byte[]> preKeyMap = new HashMap<>();
if (node.isArray()) { if (node.isArray()) {
for (JsonNode preKey : node) { for (JsonNode preKey : node) {

View file

@ -24,7 +24,6 @@ class JsonSessionStore implements SessionStore {
this.sessions.putAll(sessions); this.sessions.putAll(sessions);
} }
@Override @Override
public synchronized SessionRecord loadSession(SignalProtocolAddress remoteAddress) { public synchronized SessionRecord loadSession(SignalProtocolAddress remoteAddress) {
try { try {

View file

@ -23,7 +23,6 @@ class JsonSignedPreKeyStore implements SignedPreKeyStore {
} }
public void addSignedPreKeys(Map<Integer, byte[]> preKeys) { public void addSignedPreKeys(Map<Integer, byte[]> preKeys) {
store.putAll(preKeys); store.putAll(preKeys);
} }
@ -77,7 +76,6 @@ class JsonSignedPreKeyStore implements SignedPreKeyStore {
public JsonSignedPreKeyStore deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { public JsonSignedPreKeyStore deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
JsonNode node = jsonParser.getCodec().readTree(jsonParser); JsonNode node = jsonParser.getCodec().readTree(jsonParser);
Map<Integer, byte[]> preKeyMap = new HashMap<>(); Map<Integer, byte[]> preKeyMap = new HashMap<>();
if (node.isArray()) { if (node.isArray()) {
for (JsonNode preKey : node) { for (JsonNode preKey : node) {

View file

@ -14,13 +14,14 @@ import java.util.List;
import java.util.Map; import java.util.Map;
public class JsonThreadStore { public class JsonThreadStore {
private static final ObjectMapper jsonProcessor = new ObjectMapper();
@JsonProperty("threads") @JsonProperty("threads")
@JsonSerialize(using = JsonThreadStore.MapToListSerializer.class) @JsonSerialize(using = JsonThreadStore.MapToListSerializer.class)
@JsonDeserialize(using = ThreadsDeserializer.class) @JsonDeserialize(using = ThreadsDeserializer.class)
private Map<String, ThreadInfo> threads = new HashMap<>(); private Map<String, ThreadInfo> threads = new HashMap<>();
private static final ObjectMapper jsonProcessor = new ObjectMapper();
public void updateThread(ThreadInfo thread) { public void updateThread(ThreadInfo thread) {
threads.put(thread.id, thread); threads.put(thread.id, thread);
} }
@ -34,6 +35,7 @@ public class JsonThreadStore {
} }
public static class MapToListSerializer extends JsonSerializer<Map<?, ?>> { public static class MapToListSerializer extends JsonSerializer<Map<?, ?>> {
@Override @Override
public void serialize(final Map<?, ?> value, final JsonGenerator jgen, final SerializerProvider provider) throws IOException { public void serialize(final Map<?, ?> value, final JsonGenerator jgen, final SerializerProvider provider) throws IOException {
jgen.writeObject(value.values()); jgen.writeObject(value.values());
@ -41,6 +43,7 @@ public class JsonThreadStore {
} }
public static class ThreadsDeserializer extends JsonDeserializer<Map<String, ThreadInfo>> { public static class ThreadsDeserializer extends JsonDeserializer<Map<String, ThreadInfo>> {
@Override @Override
public Map<String, ThreadInfo> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { public Map<String, ThreadInfo> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
Map<String, ThreadInfo> threads = new HashMap<>(); Map<String, ThreadInfo> threads = new HashMap<>();

View file

@ -3,6 +3,7 @@ package org.asamk.signal.storage.threads;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
public class ThreadInfo { public class ThreadInfo {
@JsonProperty @JsonProperty
public String id; public String id;

View file

@ -33,13 +33,18 @@ public class IOUtils {
return output.toString(); return output.toString();
} }
public static void createPrivateDirectories(String path) throws IOException { public static void createPrivateDirectories(String directoryPath) throws IOException {
final Path file = new File(path).toPath(); final File file = new File(directoryPath);
if (file.exists()) {
return;
}
final Path path = file.toPath();
try { try {
Set<PosixFilePermission> perms = EnumSet.of(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE); Set<PosixFilePermission> perms = EnumSet.of(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE);
Files.createDirectories(file, PosixFilePermissions.asFileAttribute(perms)); Files.createDirectories(path, PosixFilePermissions.asFileAttribute(perms));
} catch (UnsupportedOperationException e) { } catch (UnsupportedOperationException e) {
Files.createDirectories(file); Files.createDirectories(path);
} }
} }

View file

@ -1,5 +1,8 @@
package org.asamk.signal.util; package org.asamk.signal.util;
import com.fasterxml.jackson.databind.JsonNode;
import java.io.InvalidObjectException;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.util.HashMap; import java.util.HashMap;
@ -53,4 +56,13 @@ public class Util {
return buf.toString(); return buf.toString();
} }
public static JsonNode getNotNullNode(JsonNode parent, String name) throws InvalidObjectException {
JsonNode node = parent.get(name);
if (node == null) {
throw new InvalidObjectException(String.format("Incorrect file format: expected parameter %s not found ", name));
}
return node;
}
} }