]> nmode's Git Repositories - signal-cli/blobdiff - src/main/java/org/asamk/signal/manager/Manager.java
Only send our profile key to recipient who are in our contact list
[signal-cli] / src / main / java / org / asamk / signal / manager / Manager.java
index 99b0d82a2d92fa4f5e41bebe60a21b6015d90ae3..caba52ac7c7a5977198e7f66bd5ff1e586a5554d 100644 (file)
@@ -31,7 +31,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 +80,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;
@@ -137,6 +137,7 @@ 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.zip.ZipEntry;
@@ -171,12 +172,12 @@ public class Manager implements Signal {
         return username;
     }
 
-    private SignalServiceAddress getSelfAddress() {
-        return new SignalServiceAddress(null, 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() {
@@ -215,9 +216,15 @@ public class Manager implements Signal {
 
         accountManager = getSignalServiceAccountManager();
         try {
-            if (account.isRegistered() && accountManager.getPreKeysCount() < BaseConfig.PREKEY_MINIMUM_COUNT) {
-                refreshPreKeys();
-                account.save();
+            if (account.isRegistered()) {
+                if (accountManager.getPreKeysCount() < BaseConfig.PREKEY_MINIMUM_COUNT) {
+                    refreshPreKeys();
+                    account.save();
+                }
+                if (account.getUuid() == null) {
+                    account.setUuid(accountManager.getOwnUuid());
+                    account.save();
+                }
             }
         } catch (AuthorizationFailedException e) {
             System.err.println("Authorization failed, was the number registered elsewhere?");
@@ -345,7 +352,7 @@ 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);
 
         refreshPreKeys();
 
@@ -423,10 +430,11 @@ 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, capabilities);
 
         //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
         account.setRegistered(true);
+        account.setUuid(uuid);
         account.setRegistrationLockPin(pin);
 
         refreshPreKeys();
@@ -453,11 +461,11 @@ public class Manager implements Signal {
     }
 
     private SignalServiceMessageReceiver getMessageReceiver() {
-        return new SignalServiceMessageReceiver(BaseConfig.serviceConfiguration, null, username, account.getPassword(), account.getDeviceId(), account.getSignalingKey(), BaseConfig.USER_AGENT, null, timer);
+        return new SignalServiceMessageReceiver(BaseConfig.serviceConfiguration, account.getUuid(), account.getUsername(), account.getPassword(), account.getDeviceId(), account.getSignalingKey(), BaseConfig.USER_AGENT, null, timer);
     }
 
     private SignalServiceMessageSender getMessageSender() {
-        return new SignalServiceMessageSender(BaseConfig.serviceConfiguration, null, username, account.getPassword(),
+        return new SignalServiceMessageSender(BaseConfig.serviceConfiguration, account.getUuid(), account.getUsername(), account.getPassword(),
                 account.getDeviceId(), account.getSignalProtocolStore(), BaseConfig.USER_AGENT, account.isMultiDevice(), Optional.fromNullable(messagePipe), Optional.fromNullable(unidentifiedMessagePipe), Optional.absent());
     }
 
@@ -503,12 +511,10 @@ public class Manager implements Signal {
         if (g == null) {
             throw new GroupNotFoundException(groupId);
         }
-        for (String member : g.members) {
-            if (member.equals(this.username)) {
-                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() {
@@ -529,17 +535,12 @@ 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);
 
-        // Don't send group message to ourself
-        final List<String> membersSend = new ArrayList<>(g.members);
-        membersSend.remove(this.username);
-        sendMessageLegacy(messageBuilder, membersSend);
+        messageBuilder.withExpiration(g.messageExpirationTime);
+
+        sendMessageLegacy(messageBuilder, g.getMembersWithout(account.getSelfAddress()));
     }
 
     public void sendGroupMessageReaction(String emoji, boolean remove, SignalServiceAddress targetAuthor,
@@ -547,8 +548,7 @@ public class Manager implements Signal {
             throws IOException, EncapsulatedExceptions, AttachmentInvalidException {
         SignalServiceDataMessage.Reaction reaction = new SignalServiceDataMessage.Reaction(emoji, remove, 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)
@@ -556,10 +556,7 @@ public class Manager implements Signal {
             messageBuilder.asGroupMessage(group);
         }
         final GroupInfo g = getGroupForSending(groupId);
-        // Don't send group message to ourself
-        final List<String> membersSend = new ArrayList<>(g.members);
-        membersSend.remove(this.username);
-        sendMessageLegacy(messageBuilder, membersSend);
+        sendMessageLegacy(messageBuilder, g.getMembersWithout(account.getSelfAddress()));
     }
 
     public void sendQuitGroupMessage(byte[] groupId) throws GroupNotFoundException, IOException, EncapsulatedExceptions {
@@ -571,18 +568,18 @@ public class Manager implements Signal {
                 .asGroupMessage(group);
 
         final GroupInfo g = getGroupForSending(groupId);
-        g.members.remove(this.username);
+        g.removeMember(account.getSelfAddress());
         account.getGroupStore().updateGroup(g);
 
-        sendMessageLegacy(messageBuilder, 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 {
+    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(username);
+            g.addMembers(Collections.singleton(account.getSelfAddress()));
         } else {
             g = getGroupForSending(groupId);
         }
@@ -592,31 +589,26 @@ public class Manager implements Signal {
         }
 
         if (members != null) {
-            Set<String> newMembers = new HashSet<>();
-            for (String member : members) {
-                try {
-                    member = Utils.canonicalizeNumber(member, username);
-                } catch (InvalidNumberException e) {
-                    System.err.println("Failed to add member \"" + member + "\" to group: " + e.getMessage());
-                    System.err.println("Aborting…");
-                    System.exit(1);
-                }
-                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) {
@@ -629,29 +621,24 @@ public class Manager implements Signal {
 
         SignalServiceDataMessage.Builder messageBuilder = getGroupUpdateMessageBuilder(g);
 
-        // Don't send group message to ourself
-        final List<String> membersSend = new ArrayList<>(g.members);
-        membersSend.remove(this.username);
-        sendMessageLegacy(messageBuilder, membersSend);
+        sendMessageLegacy(messageBuilder, g.getMembersWithout(account.getSelfAddress()));
         return g.groupId;
     }
 
-    private void sendUpdateGroupMessage(byte[] groupId, String recipient) throws IOException, EncapsulatedExceptions {
+    private void sendUpdateGroupMessage(byte[] groupId, SignalServiceAddress recipient) throws IOException, EncapsulatedExceptions {
         if (groupId == null) {
             return;
         }
         GroupInfo g = getGroupForSending(groupId);
 
-        if (!g.members.contains(recipient)) {
+        if (!g.isMember(recipient)) {
             return;
         }
 
         SignalServiceDataMessage.Builder messageBuilder = getGroupUpdateMessageBuilder(g);
 
         // Send group message only to the recipient who requested it
-        final List<String> membersSend = new ArrayList<>();
-        membersSend.add(recipient);
-        sendMessageLegacy(messageBuilder, membersSend);
+        sendMessageLegacy(messageBuilder, Collections.singleton(recipient));
     }
 
     private SignalServiceDataMessage.Builder getGroupUpdateMessageBuilder(GroupInfo g) {
@@ -669,18 +656,12 @@ 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, String recipient) throws IOException, EncapsulatedExceptions {
+    private void sendGroupInfoRequest(byte[] groupId, SignalServiceAddress recipient) throws IOException, EncapsulatedExceptions {
         if (groupId == null) {
             return;
         }
@@ -691,20 +672,21 @@ 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
-        final List<String> membersSend = new ArrayList<>();
-        membersSend.add(recipient);
-        sendMessageLegacy(messageBuilder, membersSend);
+        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)
-            throws EncapsulatedExceptions, AttachmentInvalidException, IOException {
+            throws EncapsulatedExceptions, AttachmentInvalidException, IOException, InvalidNumberException {
         List<String> recipients = new ArrayList<>(1);
         recipients.add(recipient);
         sendMessage(message, attachments, recipients);
@@ -713,7 +695,7 @@ public class Manager implements Signal {
     @Override
     public void sendMessage(String messageText, List<String> attachments,
                             List<String> recipients)
-            throws IOException, EncapsulatedExceptions, AttachmentInvalidException {
+            throws IOException, EncapsulatedExceptions, AttachmentInvalidException, InvalidNumberException {
         final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder().withBody(messageText);
         if (attachments != null) {
             List<SignalServiceAttachment> attachmentStreams = Utils.getSignalServiceAttachments(attachments);
@@ -731,32 +713,30 @@ public class Manager implements Signal {
 
             messageBuilder.withAttachments(attachmentPointers);
         }
-        messageBuilder.withProfileKey(account.getProfileKey().serialize());
-        sendMessageLegacy(messageBuilder, recipients);
+        sendMessageLegacy(messageBuilder, getSignalServiceAddresses(recipients));
     }
 
     public void sendMessageReaction(String emoji, boolean remove, SignalServiceAddress targetAuthor,
                                     long targetSentTimestamp, List<String> recipients)
-            throws IOException, EncapsulatedExceptions, AttachmentInvalidException {
+            throws IOException, EncapsulatedExceptions, AttachmentInvalidException, InvalidNumberException {
         SignalServiceDataMessage.Reaction reaction = new SignalServiceDataMessage.Reaction(emoji, remove, targetAuthor, targetSentTimestamp);
         final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder()
-                .withReaction(reaction)
-                .withProfileKey(account.getProfileKey().serialize());
-        sendMessageLegacy(messageBuilder, recipients);
+                .withReaction(reaction);
+        sendMessageLegacy(messageBuilder, getSignalServiceAddresses(recipients));
     }
 
     @Override
-    public void sendEndSessionMessage(List<String> recipients) throws IOException, EncapsulatedExceptions {
+    public void sendEndSessionMessage(List<String> recipients) throws IOException, EncapsulatedExceptions, InvalidNumberException {
         SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder()
                 .asEndSessionMessage();
 
-        sendMessageLegacy(messageBuilder, recipients);
+        sendMessageLegacy(messageBuilder, getSignalServiceAddresses(recipients));
     }
 
     @Override
     public String getContactName(String number) throws InvalidNumberException {
-        String canonicalizedNumber = Utils.canonicalizeNumber(number, username);
-        ContactInfo contact = account.getContactStore().getContact(canonicalizedNumber);
+        String canonicalizedNumber = Utils.canonicalizeNumber(number, account.getUsername());
+        ContactInfo contact = account.getContactStore().getContact(new SignalServiceAddress(null, canonicalizedNumber));
         if (contact == null) {
             return "";
         } else {
@@ -766,11 +746,11 @@ public class Manager implements Signal {
 
     @Override
     public void setContactName(String number, String name) throws InvalidNumberException {
-        String canonicalizedNumber = Utils.canonicalizeNumber(number, username);
-        ContactInfo contact = account.getContactStore().getContact(canonicalizedNumber);
+        String canonicalizedNumber = Utils.canonicalizeNumber(number, account.getUsername());
+        final SignalServiceAddress address = new SignalServiceAddress(null, canonicalizedNumber);
+        ContactInfo contact = account.getContactStore().getContact(address);
         if (contact == null) {
-            contact = new ContactInfo();
-            contact.number = canonicalizedNumber;
+            contact = new ContactInfo(address);
             System.err.println("Add contact " + canonicalizedNumber + " named " + name);
         } else {
             System.err.println("Updating contact " + canonicalizedNumber + " name " + contact.name + " -> " + name);
@@ -782,11 +762,11 @@ public class Manager implements Signal {
 
     @Override
     public void setContactBlocked(String number, boolean blocked) throws InvalidNumberException {
-        number = Utils.canonicalizeNumber(number, username);
-        ContactInfo contact = account.getContactStore().getContact(number);
+        number = Utils.canonicalizeNumber(number, account.getUsername());
+        final SignalServiceAddress address = new SignalServiceAddress(null, number);
+        ContactInfo contact = account.getContactStore().getContact(address);
         if (contact == null) {
-            contact = new ContactInfo();
-            contact.number = number;
+            contact = new ContactInfo(address);
             System.err.println("Adding and " + (blocked ? "blocking" : "unblocking") + " contact " + number);
         } else {
             System.err.println((blocked ? "Blocking" : "Unblocking") + " contact " + number);
@@ -833,14 +813,14 @@ 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());
         }
     }
 
     @Override
-    public byte[] updateGroup(byte[] groupId, String name, List<String> members, String avatar) throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException {
+    public byte[] updateGroup(byte[] groupId, String name, List<String> members, String avatar) throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException {
         if (groupId.length == 0) {
             groupId = null;
         }
@@ -853,19 +833,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)
-     *
-     * @param numberOrGroupId
-     * @param messageExpirationTimer
+     * 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);
     }
 
     /**
@@ -953,7 +939,7 @@ public class Manager implements Signal {
                 stickers);
     }
 
-    private static JsonStickerPack parseStickerPack(String rootPath, ZipFile zip) throws IOException, StickerPackInvalidException {
+    private static JsonStickerPack parseStickerPack(String rootPath, ZipFile zip) throws IOException {
         InputStream inputStream;
         if (zip != null) {
             inputStream = zip.getInputStream(zip.getEntry("manifest.json"));
@@ -1039,7 +1025,7 @@ public class Manager implements Signal {
     }
 
     private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient) throws IOException {
-        ContactInfo contact = account.getContactStore().getContact(recipient.getNumber().get());
+        ContactInfo contact = account.getContactStore().getContact(recipient);
         if (contact == null || contact.profileKey == null) {
             return null;
         }
@@ -1121,7 +1107,7 @@ 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<String> recipients)
+    private void sendMessageLegacy(SignalServiceDataMessage.Builder messageBuilder, Collection<SignalServiceAddress> recipients)
             throws EncapsulatedExceptions, IOException {
         List<SendMessageResult> results = sendMessage(messageBuilder, recipients);
 
@@ -1143,14 +1129,24 @@ public class Manager implements Signal {
         }
     }
 
-    private List<SendMessageResult> sendMessage(SignalServiceDataMessage.Builder messageBuilder, Collection<String> recipients)
-            throws IOException {
-        Set<SignalServiceAddress> recipientsTS = Utils.getSignalServiceAddresses(recipients, username);
-        if (recipientsTS == null) {
-            account.save();
-            return Collections.emptyList();
+    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));
+            }
         }
+        return signalServiceAddresses;
+    }
 
+    private List<SendMessageResult> sendMessage(SignalServiceDataMessage.Builder messageBuilder, Collection<SignalServiceAddress> recipients)
+            throws IOException {
         if (messagePipe == null) {
             messagePipe = getMessageReceiver().createMessagePipe();
         }
@@ -1165,7 +1161,7 @@ public class Manager implements Signal {
             if (message.getGroupInfo().isPresent()) {
                 try {
                     final boolean isRecipientUpdate = false;
-                    List<SendMessageResult> result = messageSender.sendMessage(new ArrayList<>(recipientsTS), getAccessFor(recipientsTS), isRecipientUpdate, message);
+                    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);
@@ -1176,8 +1172,8 @@ public class Manager implements Signal {
                     account.getSignalProtocolStore().saveIdentity(e.getIdentifier(), e.getIdentityKey(), TrustLevel.UNTRUSTED);
                     return Collections.emptyList();
                 }
-            } else if (recipientsTS.size() == 1 && recipientsTS.contains(getSelfAddress())) {
-                SignalServiceAddress recipient = getSelfAddress();
+            } else if (recipients.size() == 1 && recipients.contains(account.getSelfAddress())) {
+                SignalServiceAddress recipient = account.getSelfAddress();
                 final Optional<UnidentifiedAccessPair> unidentifiedAccess = getAccessFor(recipient);
                 SentTranscriptMessage transcript = new SentTranscriptMessage(Optional.of(recipient),
                         message.getTimestamp(),
@@ -1187,7 +1183,7 @@ public class Manager implements Signal {
                         false);
                 SignalServiceSyncMessage syncMessage = SignalServiceSyncMessage.forSentTranscript(transcript);
 
-                List<SendMessageResult> results = new ArrayList<>(recipientsTS.size());
+                List<SendMessageResult> results = new ArrayList<>(recipients.size());
                 try {
                     messageSender.sendMessage(syncMessage, unidentifiedAccess);
                 } catch (UntrustedIdentityException e) {
@@ -1197,13 +1193,15 @@ public class Manager implements Signal {
                 return results;
             } else {
                 // Send to all individually, so sync messages are sent correctly
-                List<SendMessageResult> results = new ArrayList<>(recipientsTS.size());
-                for (SignalServiceAddress address : recipientsTS) {
-                    ThreadInfo thread = account.getThreadStore().getThread(address.getNumber().get());
-                    if (thread != null) {
-                        messageBuilder.withExpiration(thread.messageExpirationTime);
+                List<SendMessageResult> results = new ArrayList<>(recipients.size());
+                for (SignalServiceAddress address : recipients) {
+                    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 {
@@ -1218,7 +1216,7 @@ public class Manager implements Signal {
             }
         } finally {
             if (message != null && message.isEndSession()) {
-                for (SignalServiceAddress recipient : recipientsTS) {
+                for (SignalServiceAddress recipient : recipients) {
                     handleEndSession(recipient.getNumber().get());
                 }
             }
@@ -1227,7 +1225,7 @@ public class Manager implements Signal {
     }
 
     private SignalServiceContent decryptMessage(SignalServiceEnvelope envelope) throws InvalidMetadataMessageException, ProtocolInvalidMessageException, ProtocolDuplicateMessageException, ProtocolLegacyMessageException, ProtocolInvalidKeyIdException, InvalidMetadataVersionException, ProtocolInvalidVersionException, ProtocolNoSessionException, ProtocolInvalidKeyException, ProtocolUntrustedIdentityException, SelfSendException, UnsupportedDataMessageException {
-        SignalServiceCipher cipher = new SignalServiceCipher(getSelfAddress(), account.getSignalProtocolStore(), Utils.getCertificateValidator());
+        SignalServiceCipher cipher = new SignalServiceCipher(account.getSelfAddress(), account.getSignalProtocolStore(), Utils.getCertificateValidator());
         try {
             return cipher.decrypt(envelope);
         } catch (ProtocolUntrustedIdentityException e) {
@@ -1241,11 +1239,9 @@ public class Manager implements Signal {
         account.getSignalProtocolStore().deleteAllSessions(source);
     }
 
-    private void handleSignalServiceDataMessage(SignalServiceDataMessage message, boolean isSync, String source, SignalServiceAddress destination, boolean ignoreAttachments) {
-        String threadId;
+    private void handleSignalServiceDataMessage(SignalServiceDataMessage message, boolean isSync, SignalServiceAddress source, SignalServiceAddress destination, boolean ignoreAttachments) {
         if (message.getGroupInfo().isPresent()) {
             SignalServiceGroup groupInfo = message.getGroupInfo().get();
-            threadId = Base64.encodeBytes(groupInfo.getGroupId());
             GroupInfo group = account.getGroupStore().getGroup(groupInfo.getGroupId());
             switch (groupInfo.getType()) {
                 case UPDATE:
@@ -1291,7 +1287,7 @@ public class Manager implements Signal {
                             e.printStackTrace();
                         }
                     } else {
-                        group.members.remove(source);
+                        group.removeMember(source);
                         account.getGroupStore().updateGroup(group);
                     }
                     break;
@@ -1307,25 +1303,30 @@ public class Manager implements Signal {
                     }
                     break;
             }
-        } else {
-            if (isSync) {
-                threadId = destination.getNumber().get();
-            } else {
-                threadId = source;
-            }
         }
         if (message.isEndSession()) {
-            handleEndSession(isSync ? destination.getNumber().get() : source);
+            handleEndSession(isSync ? destination.getNumber().get() : source.getNumber().get());
         }
         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.getGroupInfo().isPresent()) {
+                SignalServiceGroup groupInfo = message.getGroupInfo().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(isSync ? destination : source);
+                if (contact == null) {
+                    contact = new ContactInfo(isSync ? destination : source);
+                }
+                if (contact.messageExpirationTime != message.getExpiresInSeconds()) {
+                    contact.messageExpirationTime = message.getExpiresInSeconds();
+                    account.getContactStore().updateContact(contact);
+                }
             }
         }
         if (message.getAttachments().isPresent() && !ignoreAttachments) {
@@ -1340,19 +1341,24 @@ public class Manager implements Signal {
             }
         }
         if (message.getProfileKey().isPresent() && message.getProfileKey().get().length == 32) {
-            if (source.equals(username)) {
+            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();
-                contact.number = source;
-            }
-            contact.profileKey = Base64.encodeBytes(message.getProfileKey().get());
-            account.getContactStore().updateContact(contact);
         }
         if (message.getPreviews().isPresent()) {
             final List<SignalServiceDataMessage.Preview> previews = message.getPreviews().get();
@@ -1516,14 +1522,23 @@ public class Manager implements Signal {
             }
             if (content.getDataMessage().isPresent()) {
                 SignalServiceDataMessage message = content.getDataMessage().get();
-                handleSignalServiceDataMessage(message, false, sender.getNumber().get(), getSelfAddress(), ignoreAttachments);
+
+                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()) {
                 account.setMultiDevice(true);
                 SignalServiceSyncMessage syncMessage = content.getSyncMessage().get();
                 if (syncMessage.getSent().isPresent()) {
                     SentTranscriptMessage message = syncMessage.getSent().get();
-                    handleSignalServiceDataMessage(message.getMessage(), true, sender.getNumber().get(), message.getDestination().orNull(), ignoreAttachments);
+                    handleSignalServiceDataMessage(message.getMessage(), true, sender, message.getDestination().orNull(), ignoreAttachments);
                 }
                 if (syncMessage.getRequest().isPresent()) {
                     RequestMessage rm = syncMessage.getRequest().get();
@@ -1567,7 +1582,10 @@ public class Manager implements Signal {
                                 }
                                 syncGroup.addMembers(g.getMembers());
                                 if (!g.isActive()) {
-                                    syncGroup.members.remove(username);
+                                    syncGroup.removeMember(account.getSelfAddress());
+                                } else {
+                                    // Add ourself to the member set as it's marked as active
+                                    syncGroup.addMembers(Collections.singleton(account.getSelfAddress()));
                                 }
                                 syncGroup.blocked = g.isBlocked();
                                 if (g.getColor().isPresent()) {
@@ -1628,10 +1646,9 @@ 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().getNumber().get());
+                                ContactInfo contact = account.getContactStore().getContact(c.getAddress());
                                 if (contact == null) {
-                                    contact = new ContactInfo();
-                                    contact.number = c.getAddress().getNumber().get();
+                                    contact = new ContactInfo(c.getAddress());
                                 }
                                 if (c.getName().isPresent()) {
                                     contact.name = c.getName().get();
@@ -1647,13 +1664,7 @@ public class Manager implements Signal {
                                     account.getSignalProtocolStore().saveIdentity(verifiedMessage.getDestination().getNumber().get(), 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();
@@ -1781,10 +1792,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(username), 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));
                 }
             }
@@ -1817,7 +1827,6 @@ 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)) {
@@ -1838,7 +1847,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));
                 }
 
@@ -1898,7 +1907,7 @@ public class Manager implements Signal {
     }
 
     public ContactInfo getContact(String number) {
-        return account.getContactStore().getContact(number);
+        return account.getContactStore().getContact(new SignalServiceAddress(null, number));
     }
 
     public GroupInfo getGroup(byte[] groupId) {
@@ -1910,7 +1919,7 @@ public class Manager implements Signal {
     }
 
     public Pair<String, List<JsonIdentityKeyStore.Identity>> getIdentities(String number) throws InvalidNumberException {
-        String canonicalizedNumber = Utils.canonicalizeNumber(number, username);
+        String canonicalizedNumber = Utils.canonicalizeNumber(number, account.getUsername());
         return new Pair<>(canonicalizedNumber, account.getSignalProtocolStore().getIdentities(canonicalizedNumber));
     }
 
@@ -1995,7 +2004,7 @@ public class Manager implements Signal {
     }
 
     public String computeSafetyNumber(String theirUsername, IdentityKey theirIdentityKey) {
-        return Utils.computeSafetyNumber(username, getIdentity(), theirUsername, theirIdentityKey);
+        return Utils.computeSafetyNumber(account.getUsername(), getIdentity(), theirUsername, theirIdentityKey);
     }
 
     public interface ReceiveMessageHandler {