]> nmode's Git Repositories - signal-cli/blobdiff - src/main/java/org/asamk/signal/manager/Manager.java
Output json when receiving messages from dbus and --json parameter is given
[signal-cli] / src / main / java / org / asamk / signal / manager / Manager.java
index 8364888cbaa29b0d7e1a8af8ae3f1feee1e5d7fb..11c49d844723a60fb95731bee401446edf0e38fa 100644 (file)
@@ -21,7 +21,6 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import org.asamk.Signal;
 import org.asamk.signal.AttachmentInvalidException;
 import org.asamk.signal.GroupNotFoundException;
-import org.asamk.signal.JsonStickerPack;
 import org.asamk.signal.NotAGroupMemberException;
 import org.asamk.signal.StickerPackInvalidException;
 import org.asamk.signal.TrustLevel;
@@ -31,7 +30,6 @@ import org.asamk.signal.storage.contacts.ContactInfo;
 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.threads.ThreadInfo;
 import org.asamk.signal.util.IOUtils;
 import org.asamk.signal.util.Util;
 import org.signal.libsignal.metadata.InvalidMetadataMessageException;
@@ -81,6 +79,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceContent;
 import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
 import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
 import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
+import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
 import org.whispersystems.signalservice.api.messages.SignalServiceStickerManifestUpload;
 import org.whispersystems.signalservice.api.messages.SignalServiceStickerManifestUpload.StickerInfo;
 import org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage;
@@ -99,7 +98,6 @@ import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage
 import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
 import org.whispersystems.signalservice.api.push.ContactTokenDetails;
 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
-import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
 import org.whispersystems.signalservice.api.push.exceptions.EncapsulatedExceptions;
 import org.whispersystems.signalservice.api.push.exceptions.NetworkFailureException;
 import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
@@ -107,6 +105,7 @@ import org.whispersystems.signalservice.api.util.InvalidNumberException;
 import org.whispersystems.signalservice.api.util.SleepTimer;
 import org.whispersystems.signalservice.api.util.StreamDetails;
 import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
+import org.whispersystems.signalservice.api.util.UuidUtil;
 import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
 import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException;
 import org.whispersystems.signalservice.internal.util.Hex;
@@ -134,18 +133,17 @@ import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
-import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+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;
 
 public class Manager implements Signal {
 
-    private static final SignalServiceProfile.Capabilities capabilities = new SignalServiceProfile.Capabilities(false, false);
-
     private final String settingsPath;
     private final String dataPath;
     private final String attachmentsPath;
@@ -171,8 +169,12 @@ public class Manager implements Signal {
         return username;
     }
 
+    public SignalServiceAddress getSelfAddress() {
+        return account.getSelfAddress();
+    }
+
     private SignalServiceAccountManager getSignalServiceAccountManager() {
-        return new SignalServiceAccountManager(BaseConfig.serviceConfiguration, null, account.getUsername(), account.getPassword(), account.getDeviceId(), BaseConfig.USER_AGENT, timer);
+        return new SignalServiceAccountManager(BaseConfig.serviceConfiguration, account.getUuid(), account.getUsername(), account.getPassword(), account.getDeviceId(), BaseConfig.USER_AGENT, timer);
     }
 
     private IdentityKey getIdentity() {
@@ -188,6 +190,10 @@ public class Manager implements Signal {
     }
 
     private String getMessageCachePath(String sender) {
+        if (sender == null || sender.isEmpty()) {
+            return getMessageCachePath();
+        }
+
         return getMessageCachePath() + "/" + sender.replace("/", "_");
     }
 
@@ -206,18 +212,20 @@ public class Manager implements Signal {
             return;
         }
         account = SignalAccount.load(dataPath, username);
+        account.setResolver(this::resolveSignalServiceAddress);
 
         migrateLegacyConfigs();
 
         accountManager = getSignalServiceAccountManager();
-        try {
-            if (account.isRegistered() && accountManager.getPreKeysCount() < BaseConfig.PREKEY_MINIMUM_COUNT) {
+        if (account.isRegistered()) {
+            if (accountManager.getPreKeysCount() < BaseConfig.PREKEY_MINIMUM_COUNT) {
                 refreshPreKeys();
                 account.save();
             }
-        } catch (AuthorizationFailedException e) {
-            System.err.println("Authorization failed, was the number registered elsewhere?");
-            throw e;
+            if (account.getUuid() == null) {
+                account.setUuid(accountManager.getOwnUuid());
+                account.save();
+            }
         }
     }
 
@@ -252,9 +260,11 @@ public class Manager implements Signal {
         int registrationId = KeyHelper.generateRegistrationId(false);
         if (username == null) {
             account = SignalAccount.createTemporaryAccount(identityKey, registrationId);
+            account.setResolver(this::resolveSignalServiceAddress);
         } else {
             ProfileKey profileKey = KeyUtils.createProfileKey();
             account = SignalAccount.create(dataPath, username, identityKey, registrationId, profileKey);
+            account.setResolver(this::resolveSignalServiceAddress);
             account.save();
         }
     }
@@ -268,6 +278,7 @@ public class Manager implements Signal {
             createNewIdentity();
         }
         account.setPassword(KeyUtils.createPassword());
+        account.setUuid(null);
         accountManager = getSignalServiceAccountManager();
 
         if (voiceVerification) {
@@ -281,7 +292,7 @@ public class Manager implements Signal {
     }
 
     public void updateAccountAttributes() throws IOException {
-        accountManager.setAccountAttributes(account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, account.getRegistrationLockPin(), account.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, capabilities);
+        accountManager.setAccountAttributes(account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, account.getRegistrationLockPin(), account.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, BaseConfig.capabilities);
     }
 
     public void setProfileName(String name) throws IOException {
@@ -341,7 +352,8 @@ public class Manager implements Signal {
                 throw new IOException("Received invalid profileKey", e);
             }
         }
-        account = SignalAccount.createLinkedAccount(dataPath, username, account.getPassword(), ret.getDeviceId(), ret.getIdentity(), account.getSignalProtocolStore().getLocalRegistrationId(), account.getSignalingKey(), profileKey);
+        account = SignalAccount.createLinkedAccount(dataPath, username, ret.getUuid(), account.getPassword(), ret.getDeviceId(), ret.getIdentity(), account.getSignalProtocolStore().getLocalRegistrationId(), account.getSignalingKey(), profileKey);
+        account.setResolver(this::resolveSignalServiceAddress);
 
         refreshPreKeys();
 
@@ -419,11 +431,13 @@ public class Manager implements Signal {
         verificationCode = verificationCode.replace("-", "");
         account.setSignalingKey(KeyUtils.createSignalingKey());
         // TODO make unrestricted unidentified access configurable
-        accountManager.verifyAccountWithCode(verificationCode, account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, pin, null, getSelfUnidentifiedAccessKey(), false, capabilities);
+        UUID uuid = accountManager.verifyAccountWithCode(verificationCode, account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, pin, null, getSelfUnidentifiedAccessKey(), false, BaseConfig.capabilities);
 
         //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
         account.setRegistered(true);
+        account.setUuid(uuid);
         account.setRegistrationLockPin(pin);
+        account.getSignalProtocolStore().saveIdentity(account.getSelfAddress(), account.getSignalProtocolStore().getIdentityKeyPair().getPublicKey(), TrustLevel.TRUSTED_VERIFIED);
 
         refreshPreKeys();
         account.save();
@@ -499,12 +513,10 @@ public class Manager implements Signal {
         if (g == null) {
             throw new GroupNotFoundException(groupId);
         }
-        for (String member : g.members) {
-            if (member.equals(account.getUsername())) {
-                return g;
-            }
+        if (!g.isMember(account.getSelfAddress())) {
+            throw new NotAGroupMemberException(groupId, g.name);
         }
-        throw new NotAGroupMemberException(groupId, g.name);
+        return g;
     }
 
     public List<GroupInfo> getGroups() {
@@ -512,9 +524,9 @@ public class Manager implements Signal {
     }
 
     @Override
-    public void sendGroupMessage(String messageText, List<String> attachments,
+    public long sendGroupMessage(String messageText, List<String> attachments,
                                  byte[] groupId)
-            throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException {
+            throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException {
         final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder().withBody(messageText);
         if (attachments != null) {
             messageBuilder.withAttachments(Utils.getSignalServiceAttachments(attachments));
@@ -525,26 +537,20 @@ public class Manager implements Signal {
                     .build();
             messageBuilder.asGroupMessage(group);
         }
-        ThreadInfo thread = account.getThreadStore().getThread(Base64.encodeBytes(groupId));
-        if (thread != null) {
-            messageBuilder.withExpiration(thread.messageExpirationTime);
-        }
 
         final GroupInfo g = getGroupForSending(groupId);
 
-        final Collection<SignalServiceAddress> membersSend = getSignalServiceAddresses(g.members);
-        // Don't send group message to ourself
-        membersSend.remove(account.getSelfAddress());
-        sendMessageLegacy(messageBuilder, membersSend);
+        messageBuilder.withExpiration(g.messageExpirationTime);
+
+        return sendMessageLegacy(messageBuilder, g.getMembersWithout(account.getSelfAddress()));
     }
 
-    public void sendGroupMessageReaction(String emoji, boolean remove, SignalServiceAddress targetAuthor,
+    public void sendGroupMessageReaction(String emoji, boolean remove, String targetAuthor,
                                          long targetSentTimestamp, byte[] groupId)
             throws IOException, EncapsulatedExceptions, AttachmentInvalidException, InvalidNumberException {
-        SignalServiceDataMessage.Reaction reaction = new SignalServiceDataMessage.Reaction(emoji, remove, targetAuthor, targetSentTimestamp);
+        SignalServiceDataMessage.Reaction reaction = new SignalServiceDataMessage.Reaction(emoji, remove, canonicalizeAndResolveSignalServiceAddress(targetAuthor), targetSentTimestamp);
         final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder()
-                .withReaction(reaction)
-                .withProfileKey(account.getProfileKey().serialize());
+                .withReaction(reaction);
         if (groupId != null) {
             SignalServiceGroup group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.DELIVER)
                     .withId(groupId)
@@ -552,13 +558,10 @@ public class Manager implements Signal {
             messageBuilder.asGroupMessage(group);
         }
         final GroupInfo g = getGroupForSending(groupId);
-        final Collection<SignalServiceAddress> membersSend = getSignalServiceAddresses(g.members);
-        // Don't send group message to ourself
-        membersSend.remove(account.getSelfAddress());
-        sendMessageLegacy(messageBuilder, membersSend);
+        sendMessageLegacy(messageBuilder, g.getMembersWithout(account.getSelfAddress()));
     }
 
-    public void sendQuitGroupMessage(byte[] groupId) throws GroupNotFoundException, IOException, EncapsulatedExceptions, InvalidNumberException {
+    public void sendQuitGroupMessage(byte[] groupId) throws GroupNotFoundException, IOException, EncapsulatedExceptions {
         SignalServiceGroup group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.QUIT)
                 .withId(groupId)
                 .build();
@@ -567,18 +570,18 @@ public class Manager implements Signal {
                 .asGroupMessage(group);
 
         final GroupInfo g = getGroupForSending(groupId);
-        g.members.remove(account.getUsername());
+        g.removeMember(account.getSelfAddress());
         account.getGroupStore().updateGroup(g);
 
-        sendMessageLegacy(messageBuilder, getSignalServiceAddresses(g.members));
+        sendMessageLegacy(messageBuilder, g.getMembersWithout(account.getSelfAddress()));
     }
 
-    private byte[] sendUpdateGroupMessage(byte[] groupId, String name, Collection<String> members, String avatarFile) throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException {
+    private byte[] sendUpdateGroupMessage(byte[] groupId, String name, Collection<SignalServiceAddress> members, String avatarFile) throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException {
         GroupInfo g;
         if (groupId == null) {
             // Create new group
             g = new GroupInfo(KeyUtils.createGroupId());
-            g.members.add(account.getUsername());
+            g.addMembers(Collections.singleton(account.getSelfAddress()));
         } else {
             g = getGroupForSending(groupId);
         }
@@ -588,25 +591,26 @@ public class Manager implements Signal {
         }
 
         if (members != null) {
-            Set<String> newMembers = new HashSet<>();
-            for (String member : members) {
-                member = Utils.canonicalizeNumber(member, account.getUsername());
-                if (g.members.contains(member)) {
+            final Set<String> newE164Members = new HashSet<>();
+            for (SignalServiceAddress member : members) {
+                if (g.isMember(member) || !member.getNumber().isPresent()) {
                     continue;
                 }
-                newMembers.add(member);
-                g.members.add(member);
+                newE164Members.add(member.getNumber().get());
             }
-            final List<ContactTokenDetails> contacts = accountManager.getContacts(newMembers);
-            if (contacts.size() != newMembers.size()) {
+
+            final List<ContactTokenDetails> contacts = accountManager.getContacts(newE164Members);
+            if (contacts.size() != newE164Members.size()) {
                 // Some of the new members are not registered on Signal
                 for (ContactTokenDetails contact : contacts) {
-                    newMembers.remove(contact.getNumber());
+                    newE164Members.remove(contact.getNumber());
                 }
-                System.err.println("Failed to add members " + Util.join(", ", newMembers) + " to group: Not registered on Signal");
+                System.err.println("Failed to add members " + Util.join(", ", newE164Members) + " to group: Not registered on Signal");
                 System.err.println("Aborting…");
                 System.exit(1);
             }
+
+            g.addMembers(members);
         }
 
         if (avatarFile != null) {
@@ -619,10 +623,7 @@ public class Manager implements Signal {
 
         SignalServiceDataMessage.Builder messageBuilder = getGroupUpdateMessageBuilder(g);
 
-        final Collection<SignalServiceAddress> membersSend = getSignalServiceAddresses(g.members);
-        // Don't send group message to ourself
-        membersSend.remove(account.getSelfAddress());
-        sendMessageLegacy(messageBuilder, membersSend);
+        sendMessageLegacy(messageBuilder, g.getMembersWithout(account.getSelfAddress()));
         return g.groupId;
     }
 
@@ -632,7 +633,7 @@ public class Manager implements Signal {
         }
         GroupInfo g = getGroupForSending(groupId);
 
-        if (!g.members.contains(recipient.getNumber().get())) {
+        if (!g.isMember(recipient)) {
             return;
         }
 
@@ -657,15 +658,9 @@ public class Manager implements Signal {
             }
         }
 
-        SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder()
-                .asGroupMessage(group.build());
-
-        ThreadInfo thread = account.getThreadStore().getThread(Base64.encodeBytes(g.groupId));
-        if (thread != null) {
-            messageBuilder.withExpiration(thread.messageExpirationTime);
-        }
-
-        return messageBuilder;
+        return SignalServiceDataMessage.newBuilder()
+                .asGroupMessage(group.build())
+                .withExpiration(g.messageExpirationTime);
     }
 
     private void sendGroupInfoRequest(byte[] groupId, SignalServiceAddress recipient) throws IOException, EncapsulatedExceptions {
@@ -679,25 +674,28 @@ public class Manager implements Signal {
         SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder()
                 .asGroupMessage(group.build());
 
-        ThreadInfo thread = account.getThreadStore().getThread(Base64.encodeBytes(groupId));
-        if (thread != null) {
-            messageBuilder.withExpiration(thread.messageExpirationTime);
-        }
-
         // Send group info request message to the recipient who sent us a message with this groupId
         sendMessageLegacy(messageBuilder, Collections.singleton(recipient));
     }
 
+    private void sendReceipt(SignalServiceAddress remoteAddress, long messageId) throws IOException, UntrustedIdentityException {
+        SignalServiceReceiptMessage receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.DELIVERY,
+                Collections.singletonList(messageId),
+                System.currentTimeMillis());
+
+        getMessageSender().sendReceipt(remoteAddress, getAccessFor(remoteAddress), receiptMessage);
+    }
+
     @Override
-    public void sendMessage(String message, List<String> attachments, String recipient)
+    public long sendMessage(String message, List<String> attachments, String recipient)
             throws EncapsulatedExceptions, AttachmentInvalidException, IOException, InvalidNumberException {
         List<String> recipients = new ArrayList<>(1);
         recipients.add(recipient);
-        sendMessage(message, attachments, recipients);
+        return sendMessage(message, attachments, recipients);
     }
 
     @Override
-    public void sendMessage(String messageText, List<String> attachments,
+    public long sendMessage(String messageText, List<String> attachments,
                             List<String> recipients)
             throws IOException, EncapsulatedExceptions, AttachmentInvalidException, InvalidNumberException {
         final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder().withBody(messageText);
@@ -717,17 +715,15 @@ public class Manager implements Signal {
 
             messageBuilder.withAttachments(attachmentPointers);
         }
-        messageBuilder.withProfileKey(account.getProfileKey().serialize());
-        sendMessageLegacy(messageBuilder, getSignalServiceAddresses(recipients));
+        return sendMessageLegacy(messageBuilder, getSignalServiceAddresses(recipients));
     }
 
-    public void sendMessageReaction(String emoji, boolean remove, SignalServiceAddress targetAuthor,
+    public void sendMessageReaction(String emoji, boolean remove, String targetAuthor,
                                     long targetSentTimestamp, List<String> recipients)
             throws IOException, EncapsulatedExceptions, AttachmentInvalidException, InvalidNumberException {
-        SignalServiceDataMessage.Reaction reaction = new SignalServiceDataMessage.Reaction(emoji, remove, targetAuthor, targetSentTimestamp);
+        SignalServiceDataMessage.Reaction reaction = new SignalServiceDataMessage.Reaction(emoji, remove, canonicalizeAndResolveSignalServiceAddress(targetAuthor), targetSentTimestamp);
         final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder()
-                .withReaction(reaction)
-                .withProfileKey(account.getProfileKey().serialize());
+                .withReaction(reaction);
         sendMessageLegacy(messageBuilder, getSignalServiceAddresses(recipients));
     }
 
@@ -741,8 +737,7 @@ public class Manager implements Signal {
 
     @Override
     public String getContactName(String number) throws InvalidNumberException {
-        String canonicalizedNumber = Utils.canonicalizeNumber(number, account.getUsername());
-        ContactInfo contact = account.getContactStore().getContact(new SignalServiceAddress(null, canonicalizedNumber));
+        ContactInfo contact = account.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number));
         if (contact == null) {
             return "";
         } else {
@@ -752,14 +747,13 @@ public class Manager implements Signal {
 
     @Override
     public void setContactName(String number, String name) throws InvalidNumberException {
-        String canonicalizedNumber = Utils.canonicalizeNumber(number, account.getUsername());
-        final SignalServiceAddress address = new SignalServiceAddress(null, canonicalizedNumber);
+        final SignalServiceAddress address = canonicalizeAndResolveSignalServiceAddress(number);
         ContactInfo contact = account.getContactStore().getContact(address);
         if (contact == null) {
             contact = new ContactInfo(address);
-            System.err.println("Add contact " + canonicalizedNumber + " named " + name);
+            System.err.println("Add contact " + contact.number + " named " + name);
         } else {
-            System.err.println("Updating contact " + canonicalizedNumber + " name " + contact.name + " -> " + name);
+            System.err.println("Updating contact " + contact.number + " name " + contact.name + " -> " + name);
         }
         contact.name = name;
         account.getContactStore().updateContact(contact);
@@ -768,14 +762,16 @@ public class Manager implements Signal {
 
     @Override
     public void setContactBlocked(String number, boolean blocked) throws InvalidNumberException {
-        number = Utils.canonicalizeNumber(number, account.getUsername());
-        final SignalServiceAddress address = new SignalServiceAddress(null, number);
+        setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number), blocked);
+    }
+
+    private void setContactBlocked(SignalServiceAddress address, boolean blocked) {
         ContactInfo contact = account.getContactStore().getContact(address);
         if (contact == null) {
             contact = new ContactInfo(address);
-            System.err.println("Adding and " + (blocked ? "blocking" : "unblocking") + " contact " + number);
+            System.err.println("Adding and " + (blocked ? "blocking" : "unblocking") + " contact " + address.getNumber().orNull());
         } else {
-            System.err.println((blocked ? "Blocking" : "Unblocking") + " contact " + number);
+            System.err.println((blocked ? "Blocking" : "Unblocking") + " contact " + address.getNumber().orNull());
         }
         contact.blocked = blocked;
         account.getContactStore().updateContact(contact);
@@ -819,9 +815,9 @@ public class Manager implements Signal {
     public List<String> getGroupMembers(byte[] groupId) {
         GroupInfo group = getGroup(groupId);
         if (group == null) {
-            return new ArrayList<>();
+            return Collections.emptyList();
         } else {
-            return new ArrayList<>(group.members);
+            return new ArrayList<>(group.getMembersE164());
         }
     }
 
@@ -839,16 +835,25 @@ public class Manager implements Signal {
         if (avatar.isEmpty()) {
             avatar = null;
         }
-        return sendUpdateGroupMessage(groupId, name, members, avatar);
+        return sendUpdateGroupMessage(groupId, name, members == null ? null : getSignalServiceAddresses(members), avatar);
     }
 
     /**
-     * Change the expiration timer for a thread (number of groupId)
+     * Change the expiration timer for a contact
      */
-    public void setExpirationTimer(String numberOrGroupId, int messageExpirationTimer) {
-        ThreadInfo thread = account.getThreadStore().getThread(numberOrGroupId);
-        thread.messageExpirationTime = messageExpirationTimer;
-        account.getThreadStore().updateThread(thread);
+    public void setExpirationTimer(SignalServiceAddress address, int messageExpirationTimer) {
+        ContactInfo c = account.getContactStore().getContact(address);
+        c.messageExpirationTime = messageExpirationTimer;
+        account.getContactStore().updateContact(c);
+    }
+
+    /**
+     * Change the expiration timer for a group
+     */
+    public void setExpirationTimer(byte[] groupId, int messageExpirationTimer) {
+        GroupInfo g = account.getGroupStore().getGroup(groupId);
+        g.messageExpirationTime = messageExpirationTimer;
+        account.getGroupStore().updateGroup(g);
     }
 
     /**
@@ -996,8 +1001,16 @@ public class Manager implements Signal {
         }
     }
 
-    private byte[] getSenderCertificate() throws IOException {
-        byte[] certificate = accountManager.getSenderCertificate();
+    private byte[] getSenderCertificate() {
+        // TODO support UUID capable sender certificates
+        // byte[] certificate = accountManager.getSenderCertificate();
+        byte[] certificate;
+        try {
+            certificate = accountManager.getSenderCertificateLegacy();
+        } catch (IOException e) {
+            System.err.println("Failed to get sender certificate: " + e);
+            return null;
+        }
         // TODO cache for a day
         return certificate;
     }
@@ -1021,7 +1034,7 @@ public class Manager implements Signal {
         }
     }
 
-    private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient) throws IOException {
+    private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient) {
         ContactInfo contact = account.getContactStore().getContact(recipient);
         if (contact == null || contact.profileKey == null) {
             return null;
@@ -1029,10 +1042,16 @@ public class Manager implements Signal {
         ProfileKey theirProfileKey;
         try {
             theirProfileKey = new ProfileKey(Base64.decode(contact.profileKey));
-        } catch (InvalidInputException e) {
+        } catch (InvalidInputException | IOException e) {
             throw new AssertionError(e);
         }
-        SignalProfile targetProfile = decryptProfile(getRecipientProfile(recipient, Optional.absent()), theirProfileKey);
+        SignalProfile targetProfile;
+        try {
+            targetProfile = decryptProfile(getRecipientProfile(recipient, Optional.absent()), theirProfileKey);
+        } catch (IOException e) {
+            System.err.println("Failed to get recipient profile: " + e);
+            return null;
+        }
 
         if (targetProfile == null || targetProfile.getUnidentifiedAccess() == null) {
             return null;
@@ -1045,7 +1064,7 @@ public class Manager implements Signal {
         return UnidentifiedAccess.deriveAccessKeyFrom(theirProfileKey);
     }
 
-    private Optional<UnidentifiedAccessPair> getAccessForSync() throws IOException {
+    private Optional<UnidentifiedAccessPair> getAccessForSync() {
         byte[] selfUnidentifiedAccessKey = getSelfUnidentifiedAccessKey();
         byte[] selfUnidentifiedAccessCertificate = getSenderCertificate();
 
@@ -1063,7 +1082,7 @@ public class Manager implements Signal {
         }
     }
 
-    private List<Optional<UnidentifiedAccessPair>> getAccessFor(Collection<SignalServiceAddress> recipients) throws IOException {
+    private List<Optional<UnidentifiedAccessPair>> getAccessFor(Collection<SignalServiceAddress> recipients) {
         List<Optional<UnidentifiedAccessPair>> result = new ArrayList<>(recipients.size());
         for (SignalServiceAddress recipient : recipients) {
             result.add(getAccessFor(recipient));
@@ -1071,7 +1090,7 @@ public class Manager implements Signal {
         return result;
     }
 
-    private Optional<UnidentifiedAccessPair> getAccessFor(SignalServiceAddress recipient) throws IOException {
+    private Optional<UnidentifiedAccessPair> getAccessFor(SignalServiceAddress recipient) {
         byte[] recipientUnidentifiedAccessKey = getTargetUnidentifiedAccessKey(recipient);
         byte[] selfUnidentifiedAccessKey = getSelfUnidentifiedAccessKey();
         byte[] selfUnidentifiedAccessCertificate = getSenderCertificate();
@@ -1090,13 +1109,23 @@ public class Manager implements Signal {
         }
     }
 
+    private Optional<UnidentifiedAccess> getUnidentifiedAccess(SignalServiceAddress recipient) {
+        Optional<UnidentifiedAccessPair> unidentifiedAccess = getAccessFor(recipient);
+
+        if (unidentifiedAccess.isPresent()) {
+            return unidentifiedAccess.get().getTargetUnidentifiedAccess();
+        }
+
+        return Optional.absent();
+    }
+
     private void sendSyncMessage(SignalServiceSyncMessage message)
             throws IOException, UntrustedIdentityException {
         SignalServiceMessageSender messageSender = getMessageSender();
         try {
             messageSender.sendMessage(message, getAccessForSync());
         } catch (UntrustedIdentityException e) {
-            account.getSignalProtocolStore().saveIdentity(e.getIdentifier(), e.getIdentityKey(), TrustLevel.UNTRUSTED);
+            account.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e.getIdentifier()), e.getIdentityKey(), TrustLevel.UNTRUSTED);
             throw e;
         }
     }
@@ -1104,8 +1133,10 @@ public class Manager implements Signal {
     /**
      * This method throws an EncapsulatedExceptions exception instead of returning a list of SendMessageResult.
      */
-    private void sendMessageLegacy(SignalServiceDataMessage.Builder messageBuilder, Collection<SignalServiceAddress> recipients)
+    private long sendMessageLegacy(SignalServiceDataMessage.Builder messageBuilder, Collection<SignalServiceAddress> recipients)
             throws EncapsulatedExceptions, IOException {
+        final long timestamp = System.currentTimeMillis();
+        messageBuilder.withTimestamp(timestamp);
         List<SendMessageResult> results = sendMessage(messageBuilder, recipients);
 
         List<UntrustedIdentityException> untrustedIdentities = new LinkedList<>();
@@ -1114,30 +1145,24 @@ public class Manager implements Signal {
 
         for (SendMessageResult result : results) {
             if (result.isUnregisteredFailure()) {
-                unregisteredUsers.add(new UnregisteredUserException(result.getAddress().getNumber().get(), null));
+                unregisteredUsers.add(new UnregisteredUserException(result.getAddress().getLegacyIdentifier(), null));
             } else if (result.isNetworkFailure()) {
-                networkExceptions.add(new NetworkFailureException(result.getAddress().getNumber().get(), null));
+                networkExceptions.add(new NetworkFailureException(result.getAddress().getLegacyIdentifier(), null));
             } else if (result.getIdentityFailure() != null) {
-                untrustedIdentities.add(new UntrustedIdentityException("Untrusted", result.getAddress().getNumber().get(), result.getIdentityFailure().getIdentityKey()));
+                untrustedIdentities.add(new UntrustedIdentityException("Untrusted", result.getAddress().getLegacyIdentifier(), result.getIdentityFailure().getIdentityKey()));
             }
         }
         if (!untrustedIdentities.isEmpty() || !unregisteredUsers.isEmpty() || !networkExceptions.isEmpty()) {
             throw new EncapsulatedExceptions(untrustedIdentities, unregisteredUsers, networkExceptions);
         }
+        return timestamp;
     }
 
     private Collection<SignalServiceAddress> getSignalServiceAddresses(Collection<String> numbers) throws InvalidNumberException {
         final Set<SignalServiceAddress> signalServiceAddresses = new HashSet<>(numbers.size());
-        final String username = account.getUsername();
 
         for (String number : numbers) {
-            String canonicalizedNumber = Utils.canonicalizeNumber(number, username);
-            if (canonicalizedNumber.equals(username)) {
-                signalServiceAddresses.add(account.getSelfAddress());
-            } else {
-                // TODO get corresponding uuid
-                signalServiceAddresses.add(new SignalServiceAddress(null, canonicalizedNumber));
-            }
+            signalServiceAddresses.add(canonicalizeAndResolveSignalServiceAddress(number));
         }
         return signalServiceAddresses;
     }
@@ -1155,18 +1180,18 @@ public class Manager implements Signal {
             SignalServiceMessageSender messageSender = getMessageSender();
 
             message = messageBuilder.build();
-            if (message.getGroupInfo().isPresent()) {
+            if (message.getGroupContext().isPresent()) {
                 try {
                     final boolean isRecipientUpdate = false;
                     List<SendMessageResult> result = messageSender.sendMessage(new ArrayList<>(recipients), getAccessFor(recipients), isRecipientUpdate, message);
                     for (SendMessageResult r : result) {
                         if (r.getIdentityFailure() != null) {
-                            account.getSignalProtocolStore().saveIdentity(r.getAddress().getNumber().get(), r.getIdentityFailure().getIdentityKey(), TrustLevel.UNTRUSTED);
+                            account.getSignalProtocolStore().saveIdentity(r.getAddress(), r.getIdentityFailure().getIdentityKey(), TrustLevel.UNTRUSTED);
                         }
                     }
                     return result;
                 } catch (UntrustedIdentityException e) {
-                    account.getSignalProtocolStore().saveIdentity(e.getIdentifier(), e.getIdentityKey(), TrustLevel.UNTRUSTED);
+                    account.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e.getIdentifier()), e.getIdentityKey(), TrustLevel.UNTRUSTED);
                     return Collections.emptyList();
                 }
             } else if (recipients.size() == 1 && recipients.contains(account.getSelfAddress())) {
@@ -1184,7 +1209,7 @@ public class Manager implements Signal {
                 try {
                     messageSender.sendMessage(syncMessage, unidentifiedAccess);
                 } catch (UntrustedIdentityException e) {
-                    account.getSignalProtocolStore().saveIdentity(e.getIdentifier(), e.getIdentityKey(), TrustLevel.UNTRUSTED);
+                    account.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e.getIdentifier()), e.getIdentityKey(), TrustLevel.UNTRUSTED);
                     results.add(SendMessageResult.identityFailure(recipient, e.getIdentityKey()));
                 }
                 return results;
@@ -1192,18 +1217,20 @@ public class Manager implements Signal {
                 // Send to all individually, so sync messages are sent correctly
                 List<SendMessageResult> results = new ArrayList<>(recipients.size());
                 for (SignalServiceAddress address : recipients) {
-                    ThreadInfo thread = account.getThreadStore().getThread(address.getNumber().get());
-                    if (thread != null) {
-                        messageBuilder.withExpiration(thread.messageExpirationTime);
+                    ContactInfo contact = account.getContactStore().getContact(address);
+                    if (contact != null) {
+                        messageBuilder.withExpiration(contact.messageExpirationTime);
+                        messageBuilder.withProfileKey(account.getProfileKey().serialize());
                     } else {
                         messageBuilder.withExpiration(0);
+                        messageBuilder.withProfileKey(null);
                     }
                     message = messageBuilder.build();
                     try {
                         SendMessageResult result = messageSender.sendMessage(address, getAccessFor(address), message);
                         results.add(result);
                     } catch (UntrustedIdentityException e) {
-                        account.getSignalProtocolStore().saveIdentity(e.getIdentifier(), e.getIdentityKey(), TrustLevel.UNTRUSTED);
+                        account.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e.getIdentifier()), e.getIdentityKey(), TrustLevel.UNTRUSTED);
                         results.add(SendMessageResult.identityFailure(address, e.getIdentityKey()));
                     }
                 }
@@ -1212,33 +1239,34 @@ public class Manager implements Signal {
         } finally {
             if (message != null && message.isEndSession()) {
                 for (SignalServiceAddress recipient : recipients) {
-                    handleEndSession(recipient.getNumber().get());
+                    handleEndSession(recipient);
                 }
             }
             account.save();
         }
     }
 
-    private SignalServiceContent decryptMessage(SignalServiceEnvelope envelope) throws InvalidMetadataMessageException, ProtocolInvalidMessageException, ProtocolDuplicateMessageException, ProtocolLegacyMessageException, ProtocolInvalidKeyIdException, InvalidMetadataVersionException, ProtocolInvalidVersionException, ProtocolNoSessionException, ProtocolInvalidKeyException, ProtocolUntrustedIdentityException, SelfSendException, UnsupportedDataMessageException {
+    private SignalServiceContent decryptMessage(SignalServiceEnvelope envelope) throws InvalidMetadataMessageException, ProtocolInvalidMessageException, ProtocolDuplicateMessageException, ProtocolLegacyMessageException, ProtocolInvalidKeyIdException, InvalidMetadataVersionException, ProtocolInvalidVersionException, ProtocolNoSessionException, ProtocolInvalidKeyException, SelfSendException, UnsupportedDataMessageException, org.whispersystems.libsignal.UntrustedIdentityException {
         SignalServiceCipher cipher = new SignalServiceCipher(account.getSelfAddress(), account.getSignalProtocolStore(), Utils.getCertificateValidator());
         try {
             return cipher.decrypt(envelope);
         } catch (ProtocolUntrustedIdentityException e) {
-            // TODO We don't get the new untrusted identity from ProtocolUntrustedIdentityException anymore ... we need to get it from somewhere else
-//            account.getSignalProtocolStore().saveIdentity(e.getSender(), e.getUntrustedIdentity(), TrustLevel.UNTRUSTED);
-            throw e;
+            if (e.getCause() instanceof org.whispersystems.libsignal.UntrustedIdentityException) {
+                org.whispersystems.libsignal.UntrustedIdentityException identityException = (org.whispersystems.libsignal.UntrustedIdentityException) e.getCause();
+                account.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(identityException.getName()), identityException.getUntrustedIdentity(), TrustLevel.UNTRUSTED);
+                throw identityException;
+            }
+            throw new AssertionError(e);
         }
     }
 
-    private void handleEndSession(String source) {
+    private void handleEndSession(SignalServiceAddress source) {
         account.getSignalProtocolStore().deleteAllSessions(source);
     }
 
     private void handleSignalServiceDataMessage(SignalServiceDataMessage message, boolean isSync, SignalServiceAddress source, SignalServiceAddress destination, boolean ignoreAttachments) {
-        String threadId;
-        if (message.getGroupInfo().isPresent()) {
-            SignalServiceGroup groupInfo = message.getGroupInfo().get();
-            threadId = Base64.encodeBytes(groupInfo.getGroupId());
+        if (message.getGroupContext().isPresent() && message.getGroupContext().get().getGroupV1().isPresent()) {
+            SignalServiceGroup groupInfo = message.getGroupContext().get().getGroupV1().get();
             GroupInfo group = account.getGroupStore().getGroup(groupInfo.getGroupId());
             switch (groupInfo.getType()) {
                 case UPDATE:
@@ -1262,7 +1290,10 @@ public class Manager implements Signal {
                     }
 
                     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);
@@ -1284,7 +1315,7 @@ public class Manager implements Signal {
                             e.printStackTrace();
                         }
                     } else {
-                        group.members.remove(source.getNumber().get());
+                        group.removeMember(source);
                         account.getGroupStore().updateGroup(group);
                     }
                     break;
@@ -1300,25 +1331,31 @@ public class Manager implements Signal {
                     }
                     break;
             }
-        } else {
-            if (isSync) {
-                threadId = destination.getNumber().get();
-            } else {
-                threadId = source.getNumber().get();
-            }
         }
+        final SignalServiceAddress conversationPartnerAddress = isSync ? destination : source;
         if (message.isEndSession()) {
-            handleEndSession(isSync ? destination.getNumber().get() : source.getNumber().get());
+            handleEndSession(conversationPartnerAddress);
         }
         if (message.isExpirationUpdate() || message.getBody().isPresent()) {
-            ThreadInfo thread = account.getThreadStore().getThread(threadId);
-            if (thread == null) {
-                thread = new ThreadInfo();
-                thread.id = threadId;
-            }
-            if (thread.messageExpirationTime != message.getExpiresInSeconds()) {
-                thread.messageExpirationTime = message.getExpiresInSeconds();
-                account.getThreadStore().updateThread(thread);
+            if (message.getGroupContext().isPresent() && message.getGroupContext().get().getGroupV1().isPresent()) {
+                SignalServiceGroup groupInfo = message.getGroupContext().get().getGroupV1().get();
+                GroupInfo group = account.getGroupStore().getGroup(groupInfo.getGroupId());
+                if (group == null) {
+                    group = new GroupInfo(groupInfo.getGroupId());
+                }
+                if (group.messageExpirationTime != message.getExpiresInSeconds()) {
+                    group.messageExpirationTime = message.getExpiresInSeconds();
+                    account.getGroupStore().updateGroup(group);
+                }
+            } else {
+                ContactInfo contact = account.getContactStore().getContact(conversationPartnerAddress);
+                if (contact == null) {
+                    contact = new ContactInfo(conversationPartnerAddress);
+                }
+                if (contact.messageExpirationTime != message.getExpiresInSeconds()) {
+                    contact.messageExpirationTime = message.getExpiresInSeconds();
+                    account.getContactStore().updateContact(contact);
+                }
             }
         }
         if (message.getAttachments().isPresent() && !ignoreAttachments) {
@@ -1333,18 +1370,24 @@ public class Manager implements Signal {
             }
         }
         if (message.getProfileKey().isPresent() && message.getProfileKey().get().length == 32) {
-            if (source.equals(account.getSelfAddress())) {
+            if (source.matches(account.getSelfAddress())) {
                 try {
                     this.account.setProfileKey(new ProfileKey(message.getProfileKey().get()));
                 } catch (InvalidInputException ignored) {
                 }
+                ContactInfo contact = account.getContactStore().getContact(source);
+                if (contact != null) {
+                    contact.profileKey = Base64.encodeBytes(message.getProfileKey().get());
+                    account.getContactStore().updateContact(contact);
+                }
+            } else {
+                ContactInfo contact = account.getContactStore().getContact(source);
+                if (contact == null) {
+                    contact = new ContactInfo(source);
+                }
+                contact.profileKey = Base64.encodeBytes(message.getProfileKey().get());
+                account.getContactStore().updateContact(contact);
             }
-            ContactInfo contact = account.getContactStore().getContact(source);
-            if (contact == null) {
-                contact = new ContactInfo(source);
-            }
-            contact.profileKey = Base64.encodeBytes(message.getProfileKey().get());
-            account.getContactStore().updateContact(contact);
         }
         if (message.getPreviews().isPresent()) {
             final List<SignalServiceDataMessage.Preview> previews = message.getPreviews().get();
@@ -1368,6 +1411,7 @@ public class Manager implements Signal {
         }
         for (final File dir : Objects.requireNonNull(cachePath.listFiles())) {
             if (!dir.isDirectory()) {
+                retryFailedReceivedMessage(handler, ignoreAttachments, dir);
                 continue;
             }
 
@@ -1375,38 +1419,42 @@ public class Manager implements Signal {
                 if (!fileEntry.isFile()) {
                     continue;
                 }
-                SignalServiceEnvelope envelope;
-                try {
-                    envelope = Utils.loadEnvelope(fileEntry);
-                    if (envelope == null) {
-                        continue;
-                    }
-                } catch (IOException e) {
-                    e.printStackTrace();
-                    continue;
-                }
-                SignalServiceContent content = null;
-                if (!envelope.isReceipt()) {
-                    try {
-                        content = decryptMessage(envelope);
-                    } catch (Exception e) {
-                        continue;
-                    }
-                    handleMessage(envelope, content, ignoreAttachments);
-                }
-                account.save();
-                handler.handleMessage(envelope, content, null);
-                try {
-                    Files.delete(fileEntry.toPath());
-                } catch (IOException e) {
-                    System.err.println("Failed to delete cached message file “" + fileEntry + "”: " + e.getMessage());
-                }
+                retryFailedReceivedMessage(handler, ignoreAttachments, fileEntry);
             }
             // Try to delete directory if empty
             dir.delete();
         }
     }
 
+    private void retryFailedReceivedMessage(final ReceiveMessageHandler handler, final boolean ignoreAttachments, final File fileEntry) {
+        SignalServiceEnvelope envelope;
+        try {
+            envelope = Utils.loadEnvelope(fileEntry);
+            if (envelope == null) {
+                return;
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+            return;
+        }
+        SignalServiceContent content = null;
+        if (!envelope.isReceipt()) {
+            try {
+                content = decryptMessage(envelope);
+            } catch (Exception e) {
+                return;
+            }
+            handleMessage(envelope, content, ignoreAttachments);
+        }
+        account.save();
+        handler.handleMessage(envelope, content, null);
+        try {
+            Files.delete(fileEntry.toPath());
+        } catch (IOException e) {
+            System.err.println("Failed to delete cached message file “" + fileEntry + "”: " + e.getMessage());
+        }
+    }
+
     public void receiveMessages(long timeout, TimeUnit unit, boolean returnOnTimeout, boolean ignoreAttachments, ReceiveMessageHandler handler) throws IOException {
         retryFailedReceivedMessages(handler, ignoreAttachments);
         final SignalServiceMessageReceiver messageReceiver = getMessageReceiver();
@@ -1451,7 +1499,7 @@ public class Manager implements Signal {
                 if (!isMessageBlocked(envelope, content)) {
                     handler.handleMessage(envelope, content, exception);
                 }
-                if (!(exception instanceof ProtocolUntrustedIdentityException)) {
+                if (!(exception instanceof org.whispersystems.libsignal.UntrustedIdentityException)) {
                     File cacheFile = null;
                     try {
                         cacheFile = getMessageCacheFile(envelope.getSourceE164().get(), now, envelope.getTimestamp());
@@ -1480,15 +1528,15 @@ public class Manager implements Signal {
         } else {
             return false;
         }
-        ContactInfo sourceContact = getContact(source.getNumber().get());
+        ContactInfo sourceContact = account.getContactStore().getContact(source);
         if (sourceContact != null && sourceContact.blocked) {
             return true;
         }
 
         if (content != null && content.getDataMessage().isPresent()) {
             SignalServiceDataMessage message = content.getDataMessage().get();
-            if (message.getGroupInfo().isPresent()) {
-                SignalServiceGroup groupInfo = message.getGroupInfo().get();
+            if (message.getGroupContext().isPresent() && message.getGroupContext().get().getGroupV1().isPresent()) {
+                SignalServiceGroup groupInfo = message.getGroupContext().get().getGroupV1().get();
                 GroupInfo group = getGroup(groupInfo.getGroupId());
                 if (groupInfo.getType() == SignalServiceGroup.Type.DELIVER && group != null && group.blocked) {
                     return true;
@@ -1508,6 +1556,15 @@ public class Manager implements Signal {
             }
             if (content.getDataMessage().isPresent()) {
                 SignalServiceDataMessage message = content.getDataMessage().get();
+
+                if (content.isNeedsReceipt()) {
+                    try {
+                        sendReceipt(sender, message.getTimestamp());
+                    } catch (IOException | UntrustedIdentityException e) {
+                        e.printStackTrace();
+                    }
+                }
+
                 handleSignalServiceDataMessage(message, false, sender, account.getSelfAddress(), ignoreAttachments);
             }
             if (content.getSyncMessage().isPresent()) {
@@ -1557,12 +1614,15 @@ public class Manager implements Signal {
                                 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.members.remove(account.getUsername());
+                                    syncGroup.removeMember(account.getSelfAddress());
                                 } else {
                                     // Add ourself to the member set as it's marked as active
-                                    syncGroup.members.add(account.getUsername());
+                                    syncGroup.addMembers(Collections.singleton(account.getSelfAddress()));
                                 }
                                 syncGroup.blocked = g.isBlocked();
                                 if (g.getColor().isPresent()) {
@@ -1592,13 +1652,7 @@ public class Manager implements Signal {
                 if (syncMessage.getBlockedList().isPresent()) {
                     final BlockedListMessage blockedListMessage = syncMessage.getBlockedList().get();
                     for (SignalServiceAddress address : blockedListMessage.getAddresses()) {
-                        if (address.getNumber().isPresent()) {
-                            try {
-                                setContactBlocked(address.getNumber().get(), true);
-                            } catch (InvalidNumberException e) {
-                                e.printStackTrace();
-                            }
-                        }
+                        setContactBlocked(resolveSignalServiceAddress(address), true);
                     }
                     for (byte[] groupId : blockedListMessage.getGroupIds()) {
                         try {
@@ -1623,9 +1677,10 @@ public class Manager implements Signal {
                                 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();
@@ -1638,16 +1693,10 @@ public class Manager implements Signal {
                                 }
                                 if (c.getVerified().isPresent()) {
                                     final VerifiedMessage verifiedMessage = c.getVerified().get();
-                                    account.getSignalProtocolStore().saveIdentity(verifiedMessage.getDestination().getNumber().get(), verifiedMessage.getIdentityKey(), TrustLevel.fromVerifiedState(verifiedMessage.getVerified()));
+                                    account.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage.getDestination(), verifiedMessage.getIdentityKey(), TrustLevel.fromVerifiedState(verifiedMessage.getVerified()));
                                 }
                                 if (c.getExpirationTimer().isPresent()) {
-                                    ThreadInfo thread = account.getThreadStore().getThread(c.getAddress().getNumber().get());
-                                    if (thread == null) {
-                                        thread = new ThreadInfo();
-                                        thread.id = c.getAddress().getNumber().get();
-                                    }
-                                    thread.messageExpirationTime = c.getExpirationTimer().get();
-                                    account.getThreadStore().updateThread(thread);
+                                    contact.messageExpirationTime = c.getExpirationTimer().get();
                                 }
                                 contact.blocked = c.isBlocked();
                                 contact.inboxPosition = c.getInboxPosition().orNull();
@@ -1673,7 +1722,7 @@ public class Manager implements Signal {
                 }
                 if (syncMessage.getVerified().isPresent()) {
                     final VerifiedMessage verifiedMessage = syncMessage.getVerified().get();
-                    account.getSignalProtocolStore().saveIdentity(verifiedMessage.getDestination().getNumber().get(), verifiedMessage.getIdentityKey(), TrustLevel.fromVerifiedState(verifiedMessage.getVerified()));
+                    account.getSignalProtocolStore().setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage.getDestination()), verifiedMessage.getIdentityKey(), TrustLevel.fromVerifiedState(verifiedMessage.getVerified()));
                 }
                 if (syncMessage.getConfiguration().isPresent()) {
                     // TODO
@@ -1775,10 +1824,9 @@ public class Manager implements Signal {
             try (OutputStream fos = new FileOutputStream(groupsFile)) {
                 DeviceGroupsOutputStream out = new DeviceGroupsOutputStream(fos);
                 for (GroupInfo record : account.getGroupStore().getGroups()) {
-                    ThreadInfo info = account.getThreadStore().getThread(Base64.encodeBytes(record.groupId));
                     out.write(new DeviceGroup(record.groupId, Optional.fromNullable(record.name),
                             new ArrayList<>(record.getMembers()), createGroupAvatarAttachment(record.groupId),
-                            record.members.contains(account.getUsername()), Optional.fromNullable(info != null ? info.messageExpirationTime : null),
+                            record.isMember(account.getSelfAddress()), Optional.of(record.messageExpirationTime),
                             Optional.fromNullable(record.color), record.blocked, Optional.fromNullable(record.inboxPosition), record.archived));
                 }
             }
@@ -1811,17 +1859,9 @@ public class Manager implements Signal {
                 DeviceContactsOutputStream out = new DeviceContactsOutputStream(fos);
                 for (ContactInfo record : account.getContactStore().getContacts()) {
                     VerifiedMessage verifiedMessage = null;
-                    ThreadInfo info = account.getThreadStore().getThread(record.number);
-                    if (getIdentities().containsKey(record.number)) {
-                        JsonIdentityKeyStore.Identity currentIdentity = null;
-                        for (JsonIdentityKeyStore.Identity id : getIdentities().get(record.number)) {
-                            if (currentIdentity == null || id.getDateAdded().after(currentIdentity.getDateAdded())) {
-                                currentIdentity = id;
-                            }
-                        }
-                        if (currentIdentity != null) {
-                            verifiedMessage = new VerifiedMessage(record.getAddress(), currentIdentity.getIdentityKey(), currentIdentity.getTrustLevel().toVerifiedState(), currentIdentity.getDateAdded().getTime());
-                        }
+                    JsonIdentityKeyStore.Identity currentIdentity = account.getSignalProtocolStore().getIdentity(record.getAddress());
+                    if (currentIdentity != null) {
+                        verifiedMessage = new VerifiedMessage(record.getAddress(), currentIdentity.getIdentityKey(), currentIdentity.getTrustLevel().toVerifiedState(), currentIdentity.getDateAdded().getTime());
                     }
 
                     ProfileKey profileKey = null;
@@ -1832,7 +1872,7 @@ public class Manager implements Signal {
                     out.write(new DeviceContact(record.getAddress(), Optional.fromNullable(record.name),
                             createContactAvatarAttachment(record.number), Optional.fromNullable(record.color),
                             Optional.fromNullable(verifiedMessage), Optional.fromNullable(profileKey), record.blocked,
-                            Optional.fromNullable(info != null ? info.messageExpirationTime : null),
+                            Optional.of(record.messageExpirationTime),
                             Optional.fromNullable(record.inboxPosition), record.archived));
                 }
 
@@ -1892,20 +1932,19 @@ public class Manager implements Signal {
     }
 
     public ContactInfo getContact(String number) {
-        return account.getContactStore().getContact(new SignalServiceAddress(null, number));
+        return account.getContactStore().getContact(Util.getSignalServiceAddressFromIdentifier(number));
     }
 
     public GroupInfo getGroup(byte[] groupId) {
         return account.getGroupStore().getGroup(groupId);
     }
 
-    public Map<String, List<JsonIdentityKeyStore.Identity>> getIdentities() {
+    public List<JsonIdentityKeyStore.Identity> getIdentities() {
         return account.getSignalProtocolStore().getIdentities();
     }
 
-    public Pair<String, List<JsonIdentityKeyStore.Identity>> getIdentities(String number) throws InvalidNumberException {
-        String canonicalizedNumber = Utils.canonicalizeNumber(number, account.getUsername());
-        return new Pair<>(canonicalizedNumber, account.getSignalProtocolStore().getIdentities(canonicalizedNumber));
+    public List<JsonIdentityKeyStore.Identity> getIdentities(String number) throws InvalidNumberException {
+        return account.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number));
     }
 
     /**
@@ -1914,8 +1953,9 @@ public class Manager implements Signal {
      * @param name        username of the identity
      * @param fingerprint Fingerprint
      */
-    public boolean trustIdentityVerified(String name, byte[] fingerprint) {
-        List<JsonIdentityKeyStore.Identity> ids = account.getSignalProtocolStore().getIdentities(name);
+    public boolean trustIdentityVerified(String name, byte[] fingerprint) throws InvalidNumberException {
+        SignalServiceAddress address = canonicalizeAndResolveSignalServiceAddress(name);
+        List<JsonIdentityKeyStore.Identity> ids = account.getSignalProtocolStore().getIdentities(address);
         if (ids == null) {
             return false;
         }
@@ -1924,9 +1964,9 @@ public class Manager implements Signal {
                 continue;
             }
 
-            account.getSignalProtocolStore().saveIdentity(name, id.getIdentityKey(), TrustLevel.TRUSTED_VERIFIED);
+            account.getSignalProtocolStore().setIdentityTrustLevel(address, id.getIdentityKey(), TrustLevel.TRUSTED_VERIFIED);
             try {
-                sendVerifiedMessage(new SignalServiceAddress(null, name), id.getIdentityKey(), TrustLevel.TRUSTED_VERIFIED);
+                sendVerifiedMessage(address, id.getIdentityKey(), TrustLevel.TRUSTED_VERIFIED);
             } catch (IOException | UntrustedIdentityException e) {
                 e.printStackTrace();
             }
@@ -1942,19 +1982,20 @@ public class Manager implements Signal {
      * @param name         username of the identity
      * @param safetyNumber Safety number
      */
-    public boolean trustIdentityVerifiedSafetyNumber(String name, String safetyNumber) {
-        List<JsonIdentityKeyStore.Identity> ids = account.getSignalProtocolStore().getIdentities(name);
+    public boolean trustIdentityVerifiedSafetyNumber(String name, String safetyNumber) throws InvalidNumberException {
+        SignalServiceAddress address = canonicalizeAndResolveSignalServiceAddress(name);
+        List<JsonIdentityKeyStore.Identity> ids = account.getSignalProtocolStore().getIdentities(address);
         if (ids == null) {
             return false;
         }
         for (JsonIdentityKeyStore.Identity id : ids) {
-            if (!safetyNumber.equals(computeSafetyNumber(name, id.getIdentityKey()))) {
+            if (!safetyNumber.equals(computeSafetyNumber(address, id.getIdentityKey()))) {
                 continue;
             }
 
-            account.getSignalProtocolStore().saveIdentity(name, id.getIdentityKey(), TrustLevel.TRUSTED_VERIFIED);
+            account.getSignalProtocolStore().setIdentityTrustLevel(address, id.getIdentityKey(), TrustLevel.TRUSTED_VERIFIED);
             try {
-                sendVerifiedMessage(new SignalServiceAddress(null, name), id.getIdentityKey(), TrustLevel.TRUSTED_VERIFIED);
+                sendVerifiedMessage(address, id.getIdentityKey(), TrustLevel.TRUSTED_VERIFIED);
             } catch (IOException | UntrustedIdentityException e) {
                 e.printStackTrace();
             }
@@ -1970,15 +2011,16 @@ public class Manager implements Signal {
      * @param name username of the identity
      */
     public boolean trustIdentityAllKeys(String name) {
-        List<JsonIdentityKeyStore.Identity> ids = account.getSignalProtocolStore().getIdentities(name);
+        SignalServiceAddress address = resolveSignalServiceAddress(name);
+        List<JsonIdentityKeyStore.Identity> ids = account.getSignalProtocolStore().getIdentities(address);
         if (ids == null) {
             return false;
         }
         for (JsonIdentityKeyStore.Identity id : ids) {
             if (id.getTrustLevel() == TrustLevel.UNTRUSTED) {
-                account.getSignalProtocolStore().saveIdentity(name, id.getIdentityKey(), TrustLevel.TRUSTED_UNVERIFIED);
+                account.getSignalProtocolStore().setIdentityTrustLevel(address, id.getIdentityKey(), TrustLevel.TRUSTED_UNVERIFIED);
                 try {
-                    sendVerifiedMessage(new SignalServiceAddress(null, name), id.getIdentityKey(), TrustLevel.TRUSTED_UNVERIFIED);
+                    sendVerifiedMessage(address, id.getIdentityKey(), TrustLevel.TRUSTED_UNVERIFIED);
                 } catch (IOException | UntrustedIdentityException e) {
                     e.printStackTrace();
                 }
@@ -1988,8 +2030,27 @@ public class Manager implements Signal {
         return true;
     }
 
-    public String computeSafetyNumber(String theirUsername, IdentityKey theirIdentityKey) {
-        return Utils.computeSafetyNumber(account.getUsername(), getIdentity(), theirUsername, theirIdentityKey);
+    public String computeSafetyNumber(SignalServiceAddress theirAddress, IdentityKey theirIdentityKey) {
+        return Utils.computeSafetyNumber(account.getSelfAddress(), getIdentity(), theirAddress, theirIdentityKey);
+    }
+
+    public SignalServiceAddress canonicalizeAndResolveSignalServiceAddress(String identifier) throws InvalidNumberException {
+        String canonicalizedNumber = UuidUtil.isUuid(identifier) ? identifier : Util.canonicalizeNumber(identifier, account.getUsername());
+        return resolveSignalServiceAddress(canonicalizedNumber);
+    }
+
+    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();
+        }
+
+        return account.getRecipientStore().resolveServiceAddress(address);
     }
 
     public interface ReceiveMessageHandler {