import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
}
if (groupInfo.getMembers().isPresent()) {
- group.addMembers(groupInfo.getMembers().get());
+ group.addMembers(groupInfo.getMembers().get()
+ .stream()
+ .map(this::resolveSignalServiceAddress)
+ .collect(Collectors.toSet()));
}
account.getGroupStore().updateGroup(group);
break;
}
}
+ final SignalServiceAddress conversationPartnerAddress = isSync ? destination : source;
if (message.isEndSession()) {
- handleEndSession(isSync ? destination : source);
+ handleEndSession(conversationPartnerAddress);
}
if (message.isExpirationUpdate() || message.getBody().isPresent()) {
if (message.getGroupContext().isPresent() && message.getGroupContext().get().getGroupV1().isPresent()) {
account.getGroupStore().updateGroup(group);
}
} else {
- ContactInfo contact = account.getContactStore().getContact(isSync ? destination : source);
+ ContactInfo contact = account.getContactStore().getContact(conversationPartnerAddress);
if (contact == null) {
- contact = new ContactInfo(isSync ? destination : source);
+ contact = new ContactInfo(conversationPartnerAddress);
}
if (contact.messageExpirationTime != message.getExpiresInSeconds()) {
contact.messageExpirationTime = message.getExpiresInSeconds();
if (g.getName().isPresent()) {
syncGroup.name = g.getName().get();
}
- syncGroup.addMembers(g.getMembers());
+ syncGroup.addMembers(g.getMembers()
+ .stream()
+ .map(this::resolveSignalServiceAddress)
+ .collect(Collectors.toSet()));
if (!g.isActive()) {
syncGroup.removeMember(account.getSelfAddress());
} else {
if (syncMessage.getBlockedList().isPresent()) {
final BlockedListMessage blockedListMessage = syncMessage.getBlockedList().get();
for (SignalServiceAddress address : blockedListMessage.getAddresses()) {
- setContactBlocked(address, true);
+ setContactBlocked(resolveSignalServiceAddress(address), true);
}
for (byte[] groupId : blockedListMessage.getGroupIds()) {
try {
if (c.getAddress().matches(account.getSelfAddress()) && c.getProfileKey().isPresent()) {
account.setProfileKey(c.getProfileKey().get());
}
- ContactInfo contact = account.getContactStore().getContact(c.getAddress());
+ final SignalServiceAddress address = resolveSignalServiceAddress(c.getAddress());
+ ContactInfo contact = account.getContactStore().getContact(address);
if (contact == null) {
- contact = new ContactInfo(c.getAddress());
+ contact = new ContactInfo(address);
}
if (c.getName().isPresent()) {
contact.name = c.getName().get();
}
if (syncMessage.getVerified().isPresent()) {
final VerifiedMessage verifiedMessage = syncMessage.getVerified().get();
- account.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage.getDestination(), verifiedMessage.getIdentityKey(), TrustLevel.fromVerifiedState(verifiedMessage.getVerified()));
+ account.getSignalProtocolStore().setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage.getDestination()), verifiedMessage.getIdentityKey(), TrustLevel.fromVerifiedState(verifiedMessage.getVerified()));
}
if (syncMessage.getConfiguration().isPresent()) {
// TODO
public SignalServiceAddress resolveSignalServiceAddress(String identifier) {
SignalServiceAddress address = Util.getSignalServiceAddressFromIdentifier(identifier);
+
+ return resolveSignalServiceAddress(address);
+ }
+
+ public SignalServiceAddress resolveSignalServiceAddress(SignalServiceAddress address) {
if (address.matches(account.getSelfAddress())) {
return account.getSelfAddress();
}
- ContactInfo contactInfo = account.getContactStore().getContact(address);
- if (contactInfo == null) {
- return address;
- }
- return contactInfo.getAddress();
+ return account.getRecipientStore().resolveServiceAddress(address);
}
public interface ReceiveMessageHandler {
} else {
// Version 1: E164 user
version = 1;
+ if (!ownAddress.getNumber().isPresent() || !theirAddress.getNumber().isPresent()) {
+ return "INVALID ID";
+ }
ownId = ownAddress.getNumber().get().getBytes();
theirId = theirAddress.getNumber().get().getBytes();
}
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.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 java.nio.channels.FileLock;
import java.util.Collection;
import java.util.UUID;
+import java.util.stream.Collectors;
public class SignalAccount {
private JsonSignalProtocolStore signalProtocolStore;
private JsonGroupStore groupStore;
private JsonContactsStore contactStore;
+ private RecipientStore recipientStore;
private SignalAccount() {
jsonProcessor.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); // disable autodetect
account.signalProtocolStore = new JsonSignalProtocolStore(identityKey, registrationId);
account.groupStore = new JsonGroupStore();
account.contactStore = new JsonContactsStore();
+ account.recipientStore = new RecipientStore();
account.registered = false;
return account;
account.signalProtocolStore = new JsonSignalProtocolStore(identityKey, registrationId);
account.groupStore = new JsonGroupStore();
account.contactStore = new JsonContactsStore();
+ account.recipientStore = new RecipientStore();
account.registered = true;
account.isMultiDevice = true;
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 threadStoreNode = rootNode.get("threadStore");
if (threadStoreNode != null) {
LegacyJsonThreadStore threadStore = jsonProcessor.convertValue(threadStoreNode, LegacyJsonThreadStore.class);
.putPOJO("axolotlStore", signalProtocolStore)
.putPOJO("groupStore", groupStore)
.putPOJO("contactStore", contactStore)
+ .putPOJO("recipientStore", recipientStore)
;
try {
synchronized (fileChannel) {
return contactStore;
}
+ public RecipientStore getRecipientStore() {
+ return recipientStore;
+ }
+
public String getUsername() {
return username;
}
public ContactInfo getContact(SignalServiceAddress address) {
for (ContactInfo contact : contacts) {
if (contact.getAddress().matches(address)) {
+ if (contact.uuid == null) {
+ contact.uuid = address.getUuid().orNull();
+ } else if (contact.number == null) {
+ contact.number = address.getNumber().orNull();
+ }
+
return contact;
}
}
public void addMembers(Collection<SignalServiceAddress> addresses) {
for (SignalServiceAddress address : addresses) {
+ if (this.members.contains(address)) {
+ continue;
+ }
removeMember(address);
this.members.add(address);
}
return new SessionRecord();
}
+ public synchronized List<SessionInfo> getSessions() {
+ return sessions;
+ }
+
@Override
public synchronized List<Integer> getSubDeviceSessions(String name) {
SignalServiceAddress serviceAddress = resolveSignalServiceAddress(name);
}
}
- public static class JsonPreKeyStoreSerializer extends JsonSerializer<JsonSessionStore> {
+ public static class JsonSessionStoreSerializer extends JsonSerializer<JsonSessionStore> {
@Override
public void serialize(JsonSessionStore jsonSessionStore, JsonGenerator json, SerializerProvider serializerProvider) throws IOException {
@JsonProperty("sessionStore")
@JsonDeserialize(using = JsonSessionStore.JsonSessionStoreDeserializer.class)
- @JsonSerialize(using = JsonSessionStore.JsonPreKeyStoreSerializer.class)
+ @JsonSerialize(using = JsonSessionStore.JsonSessionStoreSerializer.class)
private JsonSessionStore sessionStore;
@JsonProperty("signedPreKeyStore")
return sessionStore.loadSession(address);
}
+ public List<SessionInfo> getSessions() {
+ return sessionStore.getSessions();
+ }
+
@Override
public List<Integer> getSubDeviceSessions(String name) {
return sessionStore.getSubDeviceSessions(name);
--- /dev/null
+package org.asamk.signal.storage.protocol;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+
+import org.whispersystems.signalservice.api.push.SignalServiceAddress;
+import org.whispersystems.signalservice.api.util.UuidUtil;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
+
+public class RecipientStore {
+
+ @JsonProperty("recipientStore")
+ @JsonDeserialize(using = RecipientStoreDeserializer.class)
+ @JsonSerialize(using = RecipientStoreSerializer.class)
+ private final Set<SignalServiceAddress> addresses = new HashSet<>();
+
+ public RecipientStore() {
+ }
+
+ public SignalServiceAddress resolveServiceAddress(SignalServiceAddress serviceAddress) {
+ if (addresses.contains(serviceAddress)) {
+ // If the Set already contains the exact address with UUID and Number,
+ // we can just return it here.
+ return serviceAddress;
+ }
+
+ for (SignalServiceAddress address : addresses) {
+ if (address.matches(serviceAddress)) {
+ return address;
+ }
+ }
+
+ if (serviceAddress.getNumber().isPresent() && serviceAddress.getUuid().isPresent()) {
+ addresses.add(serviceAddress);
+ }
+
+ return serviceAddress;
+ }
+
+ public static class RecipientStoreDeserializer extends JsonDeserializer<Set<SignalServiceAddress>> {
+
+ @Override
+ public Set<SignalServiceAddress> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
+ JsonNode node = jsonParser.getCodec().readTree(jsonParser);
+
+ Set<SignalServiceAddress> addresses = new HashSet<>();
+
+ if (node.isArray()) {
+ for (JsonNode recipient : node) {
+ String recipientName = recipient.get("name").asText();
+ UUID uuid = UuidUtil.parseOrThrow(recipient.get("uuid").asText());
+ final SignalServiceAddress serviceAddress = new SignalServiceAddress(uuid, recipientName);
+ addresses.add(serviceAddress);
+ }
+ }
+
+ return addresses;
+ }
+ }
+
+ public static class RecipientStoreSerializer extends JsonSerializer<Set<SignalServiceAddress>> {
+
+ @Override
+ public void serialize(Set<SignalServiceAddress> addresses, JsonGenerator json, SerializerProvider serializerProvider) throws IOException {
+ json.writeStartArray();
+ for (SignalServiceAddress address : addresses) {
+ json.writeStartObject();
+ json.writeStringField("name", address.getNumber().get());
+ json.writeStringField("uuid", address.getUuid().get().toString());
+ json.writeEndObject();
+ }
+ json.writeEndArray();
+ }
+ }
+}