diff --git a/build.gradle b/build.gradle index 42d638def..ffdf63e59 100644 --- a/build.gradle +++ b/build.gradle @@ -40,7 +40,7 @@ dependencies { compile('com.doomonafireball.betterpickers:library:1.5.3') { exclude group: 'com.android.support', module: 'support-v4' } - compile('com.github.ForstaLabs:libsignal-service-java:forsta-v2.3.1.8') { + compile('com.github.ForstaLabs:libsignal-service-java:forsta-v2.3.1.14') { exclude group: 'org.whispersystems', module: 'curve25519-java' exclude group: 'org.apache.httpcomponents', module: 'httpclient' } @@ -156,8 +156,8 @@ android { applicationId 'io.forsta.relay' minSdkVersion 14 targetSdkVersion 22 - versionName "0.1.61" - versionCode 161 + versionName "0.1.62" + versionCode 162 multiDexEnabled true buildConfigField "long", "BUILD_TIMESTAMP", getLastCommitTimestamp() + "L" resValue "string", "forsta_authorities", applicationId + '.provider.ccsm' diff --git a/src/io/forsta/ccsm/api/model/ForstaMessage.java b/src/io/forsta/ccsm/api/model/ForstaMessage.java index 5713cdb26..9c67c7fa9 100644 --- a/src/io/forsta/ccsm/api/model/ForstaMessage.java +++ b/src/io/forsta/ccsm/api/model/ForstaMessage.java @@ -1,6 +1,8 @@ package io.forsta.ccsm.api.model; import android.content.Context; +import android.database.Cursor; +import android.net.Uri; import android.text.Html; import android.text.Spanned; import android.text.TextUtils; @@ -28,8 +30,10 @@ import io.forsta.ccsm.util.ForstaUtils; import io.forsta.ccsm.util.InvalidMessagePayloadException; import io.forsta.securesms.attachments.Attachment; +import io.forsta.securesms.attachments.DatabaseAttachment; import io.forsta.securesms.database.DatabaseFactory; import io.forsta.securesms.database.GroupDatabase; +import io.forsta.securesms.mms.AttachmentManager; import io.forsta.securesms.recipients.Recipients; import io.forsta.securesms.util.GroupUtil; import io.forsta.securesms.util.Util; @@ -68,7 +72,7 @@ public static ForstaMessage fromJsonStringOrThrows(String messageBody) throws In ForstaMessage forstaMessage = new ForstaMessage(); try { JSONObject jsonBody = getVersion(1, messageBody); - if (forstaMessage.isContentType(jsonBody)) { + if (jsonBody != null && forstaMessage.isContentType(jsonBody)) { forstaMessage.threadUid = jsonBody.getString("threadId"); forstaMessage.messageId = jsonBody.getString("messageId"); if (jsonBody.has("threadTitle")) { @@ -96,15 +100,13 @@ public static ForstaMessage fromJsonStringOrThrows(String messageBody) throws In JSONObject distribution = jsonBody.getJSONObject("distribution"); forstaMessage.universalExpression = distribution.getString("expression"); } else { - Log.w(TAG, messageBody); - throw new InvalidMessagePayloadException("Control message type"); + throw new InvalidMessagePayloadException("Control message type or other"); } } catch (JSONException e) { - Log.e(TAG, "Invalid JSON message body"); - Log.e(TAG, messageBody); + Log.e(TAG, "Invalid JSON message body: " + e.getMessage()); throw new InvalidMessagePayloadException(e.getMessage()); } catch (Exception e) { - Log.w(TAG, "Exception occurred"); + Log.w(TAG, "Exception occurred: " + e.getMessage()); throw new InvalidMessagePayloadException(e.getMessage()); } return forstaMessage; @@ -219,7 +221,7 @@ public static String createForstaMessageBody(Context context, String richTextMes if (attachments != null) { for (Attachment attachment : messageAttachments) { JSONObject attachmentJson = new JSONObject(); - attachmentJson.put("name", attachment.getDataUri().getLastPathSegment()); + attachmentJson.put("name", ""); attachmentJson.put("size", attachment.getSize()); attachmentJson.put("type", attachment.getContentType()); attachments.put(attachmentJson); diff --git a/src/io/forsta/ccsm/service/ForstaServiceAccountManager.java b/src/io/forsta/ccsm/service/ForstaServiceAccountManager.java index 4a752a0b5..fd915e599 100644 --- a/src/io/forsta/ccsm/service/ForstaServiceAccountManager.java +++ b/src/io/forsta/ccsm/service/ForstaServiceAccountManager.java @@ -3,6 +3,8 @@ import android.content.Context; import android.os.Build; +import android.util.Log; + import io.forsta.ccsm.api.CcsmApi; import io.forsta.securesms.BuildConfig; import io.forsta.securesms.util.TextSecurePreferences; @@ -13,14 +15,16 @@ import org.whispersystems.signalservice.internal.push.PushServiceSocket; import org.whispersystems.signalservice.internal.util.StaticCredentialsProvider; +import static android.content.ContentValues.TAG; + public class ForstaServiceAccountManager extends SignalServiceAccountManager { private final TrustStore trustStore; public ForstaServiceAccountManager(String url, TrustStore trustStore, - String addr, String password, + String number, Integer deviceId, String password, String userAgent) { - super(url, trustStore, addr, password, userAgent); + super(url, trustStore, number, deviceId, password, userAgent); this.trustStore = trustStore; } @@ -43,12 +47,15 @@ public void createAccount(Context context, String addr, String password, response = CcsmApi.provisionAccount(context, attrs); } /* Retrofit ourself with the new datum provided here and through the provision act. */ - this.user = addr + "." + response.get("deviceId"); + this.user = addr; + this.deviceId = response.getInt("deviceId"); this.userAgent = userAgent; String serverUrl = response.get("serverUrl").toString(); + TextSecurePreferences.setLocalDeviceID(context, response.getInt("deviceId")); TextSecurePreferences.setServer(context, serverUrl); TextSecurePreferences.setUserAgent(context, userAgent); - StaticCredentialsProvider creds = new StaticCredentialsProvider(this.user, password, + String username = addr + "." + this.deviceId; + StaticCredentialsProvider creds = new StaticCredentialsProvider(username, password, signalingKey); this.pushServiceSocket = new PushServiceSocket(serverUrl, trustStore, creds, userAgent); } diff --git a/src/io/forsta/securesms/ConversationActivity.java b/src/io/forsta/securesms/ConversationActivity.java index f97bf5e54..d90be1a67 100644 --- a/src/io/forsta/securesms/ConversationActivity.java +++ b/src/io/forsta/securesms/ConversationActivity.java @@ -900,20 +900,20 @@ public void onReceive(Context context, Intent intent) { recipientsStaleReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - Log.w(TAG, "Group update received..."); - if (recipients != null) { - long[] ids = recipients.getIds(); - Log.w(TAG, "Looking up new recipients..."); - recipients = RecipientFactory.getRecipientsForIds(context, ids, true); - recipients.addListener(ConversationActivity.this); - onModified(recipients); - fragment.reloadList(); - } +// Log.w(TAG, "Group update received..."); +// if (recipients != null) { +// long[] ids = recipients.getIds(); +// Log.w(TAG, "Looking up new recipients..."); +// recipients = RecipientFactory.getRecipientsForIds(context, ids, true); +// recipients.addListener(ConversationActivity.this); +// onModified(recipients); +// fragment.reloadList(); +// } } }; IntentFilter staleFilter = new IntentFilter(); - staleFilter.addAction(GroupDatabase.DATABASE_UPDATE_ACTION); +// staleFilter.addAction(GroupDatabase.DATABASE_UPDATE_ACTION); staleFilter.addAction(RecipientFactory.RECIPIENT_CLEAR_ACTION); registerReceiver(securityUpdateReceiver, @@ -1161,7 +1161,7 @@ private void sendMediaMessage(final boolean forceSms, final long expiresIn, fina sendMediaMessage(forceSms, getMessage(), attachmentManager.buildSlideDeck(), expiresIn, subscriptionId); } - private ListenableFuture sendMediaMessage(final boolean forceSms, String body, SlideDeck slideDeck, final long expiresIn, final int subscriptionId) + private ListenableFuture sendMediaMessage(final boolean forceSms, String body, final SlideDeck slideDeck, final long expiresIn, final int subscriptionId) throws InvalidMessageException { final SettableFuture future = new SettableFuture<>(); @@ -1184,6 +1184,7 @@ protected Long doInBackground(OutgoingMediaMessage... messages) { OutgoingMediaMessage message = messages[0]; ForstaThread threadData = DatabaseFactory.getThreadDatabase(context).getForstaThread(threadId); + List slides = slideDeck.getSlides(); message.setForstaJsonBody(context, threadData); diff --git a/src/io/forsta/securesms/crypto/SessionUtil.java b/src/io/forsta/securesms/crypto/SessionUtil.java index c06073c8f..fac7aec61 100644 --- a/src/io/forsta/securesms/crypto/SessionUtil.java +++ b/src/io/forsta/securesms/crypto/SessionUtil.java @@ -9,16 +9,22 @@ import org.whispersystems.libsignal.state.SessionStore; import org.whispersystems.signalservice.api.push.SignalServiceAddress; +import java.util.List; + public class SessionUtil { public static boolean hasSession(Context context, MasterSecret masterSecret, Recipient recipient) { return hasSession(context, masterSecret, recipient.getNumber()); } - public static boolean hasSession(Context context, MasterSecret masterSecret, @NonNull String number) { - SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret); - SignalProtocolAddress axolotlAddress = new SignalProtocolAddress(number, SignalServiceAddress.DEFAULT_DEVICE_ID); - - return sessionStore.containsSession(axolotlAddress); + public static boolean hasSession(Context context, MasterSecret masterSecret, @NonNull String addr) { + SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret); + List devices = sessionStore.getDeviceSessions(addr); + for (int device : devices) { + if (sessionStore.containsSession(new SignalProtocolAddress(addr, device))) { + return true; + } + } + return false; } } diff --git a/src/io/forsta/securesms/crypto/storage/SignalProtocolStoreImpl.java b/src/io/forsta/securesms/crypto/storage/SignalProtocolStoreImpl.java index b6dd844ce..d78cb4447 100644 --- a/src/io/forsta/securesms/crypto/storage/SignalProtocolStoreImpl.java +++ b/src/io/forsta/securesms/crypto/storage/SignalProtocolStoreImpl.java @@ -77,8 +77,8 @@ public SessionRecord loadSession(SignalProtocolAddress axolotlAddress) { } @Override - public List getSubDeviceSessions(String number) { - return sessionStore.getSubDeviceSessions(number); + public List getDeviceSessions(String number) { + return sessionStore.getDeviceSessions(number); } @Override diff --git a/src/io/forsta/securesms/crypto/storage/TextSecureSessionStore.java b/src/io/forsta/securesms/crypto/storage/TextSecureSessionStore.java index 101bf14a4..f6120cae1 100644 --- a/src/io/forsta/securesms/crypto/storage/TextSecureSessionStore.java +++ b/src/io/forsta/securesms/crypto/storage/TextSecureSessionStore.java @@ -119,9 +119,7 @@ public void deleteSession(SignalProtocolAddress address) { @Override public void deleteAllSessions(String name) { - List devices = getSubDeviceSessions(name); - - deleteSession(new SignalProtocolAddress(name, SignalServiceAddress.DEFAULT_DEVICE_ID)); + List devices = getDeviceSessions(name); for (int device : devices) { deleteSession(new SignalProtocolAddress(name, device)); @@ -129,7 +127,7 @@ public void deleteAllSessions(String name) { } @Override - public List getSubDeviceSessions(String name) { + public List getDeviceSessions(String name) { long recipientId = RecipientFactory.getRecipientsFromString(context, name, true).getPrimaryRecipient().getRecipientId(); List results = new LinkedList<>(); File parent = getSessionDirectory(); @@ -142,8 +140,13 @@ public List getSubDeviceSessions(String name) { String[] parts = child.split("[.]", 2); long sessionRecipientId = Long.parseLong(parts[0]); - if (sessionRecipientId == recipientId && parts.length > 1) { - results.add(Integer.parseInt(parts[1])); + if (sessionRecipientId == recipientId) { + if (parts.length > 1) { + results.add(Integer.parseInt(parts[1])); + } else { + /* Legacy session entry that treated device id as special */ + results.add(new Integer(1)); + } } } catch (NumberFormatException e) { Log.w(TAG, e); @@ -186,13 +189,10 @@ private File getSessionDirectory() { return directory; } - private String getSessionName(SignalProtocolAddress axolotlAddress) { - Recipient recipient = RecipientFactory.getRecipientsFromString(context, axolotlAddress.getName(), true) - .getPrimaryRecipient(); - long recipientId = recipient.getRecipientId(); - int deviceId = axolotlAddress.getDeviceId(); - - return recipientId + (deviceId == SignalServiceAddress.DEFAULT_DEVICE_ID ? "" : "." + deviceId); + private String getSessionName(SignalProtocolAddress signalAddr) { + Recipient recipient = RecipientFactory.getRecipientsFromString(context, signalAddr.getName(), true) + .getPrimaryRecipient(); + return recipient.getRecipientId() + "." + signalAddr.getDeviceId(); } private @Nullable SignalProtocolAddress getAddressName(File sessionFile) { @@ -202,8 +202,11 @@ private String getSessionName(SignalProtocolAddress axolotlAddress) { int deviceId; - if (parts.length > 1) deviceId = Integer.parseInt(parts[1]); - else deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID; + if (parts.length > 1) { + deviceId = Integer.parseInt(parts[1]); + } else { + deviceId = 1; // Legacy session entry without device ID is 1 + } return new SignalProtocolAddress(recipient.getNumber(), deviceId); } catch (NumberFormatException e) { diff --git a/src/io/forsta/securesms/database/loaders/DeviceListLoader.java b/src/io/forsta/securesms/database/loaders/DeviceListLoader.java index 6df077101..6bc52bbd3 100644 --- a/src/io/forsta/securesms/database/loaders/DeviceListLoader.java +++ b/src/io/forsta/securesms/database/loaders/DeviceListLoader.java @@ -30,15 +30,7 @@ public List loadInBackground() { try { List devices = accountManager.getDevices(); Iterator iterator = devices.iterator(); - - while (iterator.hasNext()) { - if ((iterator.next().getId() == SignalServiceAddress.DEFAULT_DEVICE_ID)) { - iterator.remove(); - } - } - Collections.sort(devices, new DeviceInfoComparator()); - return devices; } catch (IOException e) { Log.w(TAG, e); diff --git a/src/io/forsta/securesms/dependencies/TextSecureCommunicationModule.java b/src/io/forsta/securesms/dependencies/TextSecureCommunicationModule.java index deb10dba2..7377919b4 100644 --- a/src/io/forsta/securesms/dependencies/TextSecureCommunicationModule.java +++ b/src/io/forsta/securesms/dependencies/TextSecureCommunicationModule.java @@ -61,6 +61,7 @@ public TextSecureCommunicationModule(Context context) { return new ForstaServiceAccountManager(TextSecurePreferences.getServer(context), new TextSecurePushTrustStore(context), TextSecurePreferences.getLocalNumber(context), + TextSecurePreferences.getLocalDeviceId(context), TextSecurePreferences.getPushServerPassword(context), TextSecurePreferences.getUserAgent(context)); } @@ -72,6 +73,7 @@ public SignalServiceMessageSender create() { return new SignalServiceMessageSender(TextSecurePreferences.getServer(context), new TextSecurePushTrustStore(context), TextSecurePreferences.getLocalNumber(context), + TextSecurePreferences.getLocalDeviceId(context), TextSecurePreferences.getPushServerPassword(context), new SignalProtocolStoreImpl(context), TextSecurePreferences.getUserAgent(context), @@ -101,7 +103,8 @@ private DynamicCredentialsProvider(Context context) { @Override public String getUser() { - return TextSecurePreferences.getLocalNumber(context); + return TextSecurePreferences.getLocalNumber(context) + "." + TextSecurePreferences.getLocalDeviceId(context); + } @Override diff --git a/src/io/forsta/securesms/jobs/PushDecryptJob.java b/src/io/forsta/securesms/jobs/PushDecryptJob.java index 5fdf44400..6caff0830 100644 --- a/src/io/forsta/securesms/jobs/PushDecryptJob.java +++ b/src/io/forsta/securesms/jobs/PushDecryptJob.java @@ -178,25 +178,24 @@ private void handleMessage(MasterSecretUnion masterSecret, SignalServiceEnvelope } } catch (InvalidVersionException e) { Log.w(TAG, e); - handleInvalidVersionMessage(masterSecret, envelope, smsMessageId); +// handleInvalidVersionMessage(masterSecret, envelope, smsMessageId); } catch (InvalidMessageException | InvalidKeyIdException | InvalidKeyException | MmsException e) { Log.w(TAG, e); - handleCorruptMessage(masterSecret, envelope, smsMessageId); +// handleCorruptMessage(masterSecret, envelope, smsMessageId); } catch (NoSessionException e) { Log.w(TAG, e); - handleNoSessionMessage(masterSecret, envelope, smsMessageId); +// handleNoSessionMessage(masterSecret, envelope, smsMessageId); } catch (LegacyMessageException e) { Log.w(TAG, e); - handleLegacyMessage(masterSecret, envelope, smsMessageId); +// handleLegacyMessage(masterSecret, envelope, smsMessageId); } catch (DuplicateMessageException e) { Log.w(TAG, e); handleDuplicateMessage(masterSecret, envelope, smsMessageId); } catch (UntrustedIdentityException e) { Log.w(TAG, e); - handleUntrustedIdentityMessage(masterSecret, envelope, smsMessageId); +// handleUntrustedIdentityMessage(masterSecret, envelope, smsMessageId); } catch (InvalidMessagePayloadException e) { Log.e(TAG, "Invalid Forsta message body"); - Log.e(TAG, e.getMessage()); e.printStackTrace(); } } @@ -346,7 +345,12 @@ private void handleMediaMessage(@NonNull MasterSecretUnion masterSecret, message.getAttachments()); ForstaMessage forstaMessage = ForstaMessage.fromJsonStringOrThrows(body); + Recipients recipients = getDistributionRecipients(forstaMessage.getUniversalExpression()); + DirectoryHelper.refreshDirectoryFor(context, masterSecret.getMasterSecret().get(), recipients); + // Refresh recipients cache on message receive to populate new users. + RecipientFactory.clearCache(context); + recipients = RecipientFactory.getRecipientsFromStrings(context, recipients.toNumberStringList(false), false); long threadId = DatabaseFactory.getThreadDatabase(context).getOrAllocateThreadId(recipients, forstaMessage); if (message.getExpiresInSeconds() != DatabaseFactory.getThreadPreferenceDatabase(context).getExpireMessages(threadId)) { diff --git a/src/io/forsta/securesms/jobs/PushMediaSendJob.java b/src/io/forsta/securesms/jobs/PushMediaSendJob.java index dcf7667e1..1f24ac2f6 100644 --- a/src/io/forsta/securesms/jobs/PushMediaSendJob.java +++ b/src/io/forsta/securesms/jobs/PushMediaSendJob.java @@ -3,18 +3,19 @@ import android.content.Context; import android.util.Log; -import io.forsta.ccsm.database.model.ForstaUser; import io.forsta.securesms.ApplicationContext; import io.forsta.securesms.attachments.Attachment; import io.forsta.securesms.crypto.MasterSecret; import io.forsta.securesms.database.DatabaseFactory; import io.forsta.securesms.database.MmsDatabase; import io.forsta.securesms.database.NoSuchMessageException; +import io.forsta.securesms.database.documents.NetworkFailure; import io.forsta.securesms.dependencies.InjectableType; import io.forsta.securesms.mms.MediaConstraints; import io.forsta.securesms.mms.OutgoingMediaMessage; import io.forsta.securesms.recipients.Recipient; import io.forsta.securesms.recipients.RecipientFactory; +import io.forsta.securesms.recipients.RecipientFormattingException; import io.forsta.securesms.recipients.Recipients; import io.forsta.securesms.service.ExpiringMessageManager; import io.forsta.securesms.transport.InsecureFallbackApprovalException; @@ -28,6 +29,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.EncapsulatedExceptions; +import org.whispersystems.signalservice.api.push.exceptions.NetworkFailureException; import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; import org.whispersystems.signalservice.api.util.InvalidNumberException; @@ -67,38 +69,63 @@ public void onAdded() { @Override public void onSend(MasterSecret masterSecret) - throws RetryLaterException, MmsException, NoSuchMessageException, + throws RetryLaterException, MmsException, IOException, NoSuchMessageException, UndeliverableMessageException { ExpiringMessageManager expirationManager = ApplicationContext.getInstance(context).getExpiringMessageManager(); - MmsDatabase database = DatabaseFactory.getMmsDatabase(context); - OutgoingMediaMessage message = database.getOutgoingMessage(masterSecret, messageId); - + MmsDatabase database = DatabaseFactory.getMmsDatabase(context); + OutgoingMediaMessage message = database.getOutgoingMessage(masterSecret, messageId); try { - Log.w(TAG, "Sending message: " + messageId); - processMessage(masterSecret, database, message, expirationManager); - } catch (InsecureFallbackApprovalException ifae) { - Log.w(TAG, ifae); - // XXX For now, there should be no insecure messages. Notify user by checking Recipients cache before submitting. - } catch (UntrustedIdentityException uie) { - Log.w(TAG, uie); - Log.w(TAG, "Media Message. Auto handling untrusted identity, Single recipient."); - Recipients recipients = RecipientFactory.getRecipientsFromString(context, uie.getE164Number(), false); - long recipientId = recipients.getPrimaryRecipient().getRecipientId(); - IdentityKey identityKey = uie.getIdentityKey(); - DatabaseFactory.getIdentityDatabase(context).saveIdentity(recipientId, identityKey); - try { - // Message was not sent to any recipients. Reprocess. - processMessage(masterSecret, database, message, expirationManager); - } catch (InsecureFallbackApprovalException e) { - e.printStackTrace(); - } catch (EncapsulatedExceptions encapsulatedExceptions) { - encapsulatedExceptions.printStackTrace(); - } catch (UntrustedIdentityException e) { - e.printStackTrace(); + deliver(masterSecret, message, message.getRecipients()); + database.markAsPush(messageId); + database.markAsSecure(messageId); + database.markAsSent(messageId); + markAttachmentsUploaded(messageId, message.getAttachments()); + + if (message.getExpiresIn() > 0 && !message.isExpirationUpdate()) { + database.markExpireStarted(messageId); + expirationManager.scheduleDeletion(messageId, true, message.getExpiresIn()); } - } catch (EncapsulatedExceptions ee) { - handleMultipleUntrustedIdentities(ee, masterSecret, message); + } catch (InvalidNumberException | RecipientFormattingException | UndeliverableMessageException e) { + Log.w(TAG, e); + database.markAsSentFailed(messageId); + notifyMediaMessageDeliveryFailed(context, messageId); + } catch (EncapsulatedExceptions e) { + Log.w(TAG, e); + List failures = new LinkedList<>(); + for (NetworkFailureException nfe : e.getNetworkExceptions()) { + Recipient recipient = RecipientFactory.getRecipientsFromString(context, nfe.getE164number(), false).getPrimaryRecipient(); + failures.add(new NetworkFailure(recipient.getRecipientId())); + } + + List untrustedRecipients = new ArrayList<>(); + for (UntrustedIdentityException uie : e.getUntrustedIdentityExceptions()) { + untrustedRecipients.add(uie.getE164Number()); + acceptIndentityKey(uie); + } + + if (untrustedRecipients.size() > 0) { + Recipients failedRecipients = RecipientFactory.getRecipientsFromStrings(context, untrustedRecipients, false); + try { + deliver(masterSecret, message, failedRecipients); + } catch (InvalidNumberException | EncapsulatedExceptions | RecipientFormattingException e1) { + e1.printStackTrace(); + } + } + + database.addFailures(messageId, failures); + database.markAsPush(messageId); + + if (e.getNetworkExceptions().isEmpty()) { + database.markAsSecure(messageId); + database.markAsSent(messageId); + markAttachmentsUploaded(messageId, message.getAttachments()); + } else { + database.markAsSentFailed(messageId); + notifyMediaMessageDeliveryFailed(context, messageId); + } + } catch (IllegalStateException e) { + Log.w(TAG, "Something went wildly wrong: ", e); } } @@ -116,53 +143,28 @@ public void onCanceled() { notifyMediaMessageDeliveryFailed(context, messageId); } - private void deliver(MasterSecret masterSecret, OutgoingMediaMessage message) - throws RetryLaterException, InsecureFallbackApprovalException, UntrustedIdentityException, - UndeliverableMessageException, EncapsulatedExceptions + private void deliver(MasterSecret masterSecret, OutgoingMediaMessage message, Recipients recipients) + throws IOException, RecipientFormattingException, InvalidNumberException, + EncapsulatedExceptions, UndeliverableMessageException { - if (message.getRecipients() == null || - message.getRecipients().getPrimaryRecipient() == null || - message.getRecipients().getPrimaryRecipient().getNumber() == null) - { + if (recipients == null || recipients.getPrimaryRecipient() == null || recipients.getPrimaryRecipient().getNumber() == null) { throw new UndeliverableMessageException("No destination address."); } SignalServiceMessageSender messageSender = messageSenderFactory.create(); - - try { - SignalServiceDataMessage mediaMessage = createSignalServiceDataMessage(masterSecret, message); - - if (!message.getRecipients().isSingleRecipient()) { - List addresses = getPushAddresses(message.getRecipients()); - messageSender.sendMessage(addresses, mediaMessage); - } else { - SignalServiceAddress address = getPushAddress(message.getRecipients().getPrimaryRecipient().getNumber()); - messageSender.sendMessage(address, mediaMessage); - } - } catch (InvalidNumberException | UnregisteredUserException e) { - Log.w(TAG, e); - throw new InsecureFallbackApprovalException(e); - } catch (FileNotFoundException e) { - Log.w(TAG, e); - throw new UndeliverableMessageException(e); - } catch (IOException e) { - Log.w(TAG, e); - throw new RetryLaterException(e); - } + SignalServiceDataMessage mediaMessage = createSignalServiceDataMessage(masterSecret, message); + List addresses = getPushAddresses(recipients); + Log.w(TAG, "Sending message: " + messageId); + messageSender.sendMessage(addresses, mediaMessage); } - private void processMessage(MasterSecret masterSecret, MmsDatabase database, OutgoingMediaMessage message, ExpiringMessageManager expirationManager) - throws EncapsulatedExceptions, RetryLaterException, UndeliverableMessageException, UntrustedIdentityException, InsecureFallbackApprovalException { - deliver(masterSecret, message); - database.markAsPush(messageId); - database.markAsSecure(messageId); - database.markAsSent(messageId); - markAttachmentsUploaded(messageId, message.getAttachments()); - - if (message.getExpiresIn() > 0 && !message.isExpirationUpdate()) { - database.markExpireStarted(messageId); - expirationManager.scheduleDeletion(messageId, true, message.getExpiresIn()); - } + private void acceptIndentityKey(UntrustedIdentityException uie) { + Log.w(TAG, uie); + Log.w(TAG, "Media Message. Auto handling untrusted identity."); + Recipients recipients = RecipientFactory.getRecipientsFromString(context, uie.getE164Number(), false); + long recipientId = recipients.getPrimaryRecipient().getRecipientId(); + IdentityKey identityKey = uie.getIdentityKey(); + DatabaseFactory.getIdentityDatabase(context).saveIdentity(recipientId, identityKey); } private SignalServiceDataMessage createSignalServiceDataMessage(MasterSecret masterSecret, OutgoingMediaMessage message) throws UndeliverableMessageException { @@ -178,42 +180,6 @@ private SignalServiceDataMessage createSignalServiceDataMessage(MasterSecret mas return mediaMessage; } - private void handleMultipleUntrustedIdentities(EncapsulatedExceptions ee, MasterSecret masterSecret, OutgoingMediaMessage message) { - Log.w(TAG, "Media message. Auto handling untrusted identity. Multiple recipients."); - if (ee.getUntrustedIdentityExceptions().size() > 0) { - List untrustedIdentities = ee.getUntrustedIdentityExceptions(); - List untrustedRecipients = new ArrayList<>(); - for (UntrustedIdentityException uie : untrustedIdentities) { - untrustedRecipients.add(uie.getE164Number()); - Recipients identityRecipients = RecipientFactory.getRecipientsFromString(context, uie.getE164Number(), false); - long uieRecipientId = identityRecipients.getPrimaryRecipient().getRecipientId(); - IdentityKey identityKey = uie.getIdentityKey(); - DatabaseFactory.getIdentityDatabase(context).saveIdentity(uieRecipientId, identityKey); - } - - try { - // Resend message to each untrusted identity. - if (untrustedRecipients.size() > 0) { - Recipients resendRecipients = RecipientFactory.getRecipientsFromStrings(context, untrustedRecipients, false); - List addresses = getPushAddresses(resendRecipients); - SignalServiceMessageSender messageSender = messageSenderFactory.create(); - SignalServiceDataMessage dataMessage = createSignalServiceDataMessage(masterSecret, message); - messageSender.sendMessage(addresses, dataMessage); - } - } catch (EncapsulatedExceptions encapsulatedExceptions) { - encapsulatedExceptions.printStackTrace(); - } catch (InvalidNumberException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } catch (UndeliverableMessageException e) { - e.printStackTrace(); - } catch (Exception e) { - Log.e(TAG, "Fatal message send exception." + e.getMessage()); - } - } - } - private List getPushAddresses(Recipients recipients) throws InvalidNumberException { List addresses = new LinkedList<>(); diff --git a/src/io/forsta/securesms/mms/IncomingMediaMessage.java b/src/io/forsta/securesms/mms/IncomingMediaMessage.java index 81c27972c..d068b2980 100644 --- a/src/io/forsta/securesms/mms/IncomingMediaMessage.java +++ b/src/io/forsta/securesms/mms/IncomingMediaMessage.java @@ -24,7 +24,6 @@ public class IncomingMediaMessage { private final int subscriptionId; private final long expiresIn; private final boolean expirationUpdate; - private ForstaMessage forstaMessage; private final List to = new LinkedList<>(); private final List cc = new LinkedList<>(); @@ -76,14 +75,6 @@ public IncomingMediaMessage(MasterSecretUnion masterSecret, this.attachments.addAll(PointerAttachment.forPointers(masterSecret, attachments)); } - public void setForstaMessage(ForstaMessage forstaMessage) { - this.forstaMessage = forstaMessage; - } - - public ForstaMessage getForstaMessage() { - return forstaMessage; - } - public int getSubscriptionId() { return subscriptionId; } diff --git a/src/io/forsta/securesms/push/TextSecureCommunicationFactory.java b/src/io/forsta/securesms/push/TextSecureCommunicationFactory.java index af193e633..1d50314d3 100644 --- a/src/io/forsta/securesms/push/TextSecureCommunicationFactory.java +++ b/src/io/forsta/securesms/push/TextSecureCommunicationFactory.java @@ -11,6 +11,7 @@ public static ForstaServiceAccountManager createManager(Context context) { return new ForstaServiceAccountManager(TextSecurePreferences.getServer(context), new TextSecurePushTrustStore(context), TextSecurePreferences.getLocalNumber(context), + TextSecurePreferences.getLocalDeviceId(context), TextSecurePreferences.getPushServerPassword(context), TextSecurePreferences.getUserAgent(context)); } @@ -20,6 +21,7 @@ public static ForstaServiceAccountManager createManager(Context context, String return new ForstaServiceAccountManager(TextSecurePreferences.getServer(context), new TextSecurePushTrustStore(context), addr, + new Integer(-1), password, TextSecurePreferences.getUserAgent(context)); } diff --git a/src/io/forsta/securesms/sms/IncomingTextMessage.java b/src/io/forsta/securesms/sms/IncomingTextMessage.java index 9c720cf3b..3bc315f21 100644 --- a/src/io/forsta/securesms/sms/IncomingTextMessage.java +++ b/src/io/forsta/securesms/sms/IncomingTextMessage.java @@ -39,12 +39,11 @@ public IncomingTextMessage[] newArray(int size) { private final boolean push; private final int subscriptionId; private final long expiresInMillis; - private ForstaMessage forstaMessage; public IncomingTextMessage(SmsMessage message, int subscriptionId) { this.message = message.getDisplayMessageBody(); this.sender = message.getDisplayOriginatingAddress(); - this.senderDeviceId = SignalServiceAddress.DEFAULT_DEVICE_ID; + this.senderDeviceId = -1; // XXX This code is dead! this.protocol = message.getProtocolIdentifier(); this.serviceCenterAddress = message.getServiceCenterAddress(); this.replyPathPresent = message.isReplyPathPresent(); @@ -107,7 +106,6 @@ public IncomingTextMessage(IncomingTextMessage base, String newBody) { this.push = base.isPush(); this.subscriptionId = base.getSubscriptionId(); this.expiresInMillis = base.getExpiresIn(); - this.forstaMessage = base.getForstaMessage(); } public IncomingTextMessage(List fragments) { @@ -135,7 +133,7 @@ protected IncomingTextMessage(String sender, String groupId) { this.message = ""; this.sender = sender; - this.senderDeviceId = SignalServiceAddress.DEFAULT_DEVICE_ID; + this.senderDeviceId = -1; // XXX This code is dead! this.protocol = 31338; this.serviceCenterAddress = "Outgoing"; this.replyPathPresent = true; @@ -147,14 +145,6 @@ protected IncomingTextMessage(String sender, String groupId) this.expiresInMillis = 0; } - public void setForstaMessage(ForstaMessage forstaMessage) { - this.forstaMessage = forstaMessage; - } - - public ForstaMessage getForstaMessage() { - return forstaMessage; - } - public int getSubscriptionId() { return subscriptionId; } diff --git a/src/io/forsta/securesms/sms/MessageSender.java b/src/io/forsta/securesms/sms/MessageSender.java index df472dfdb..dd2c7c4d7 100644 --- a/src/io/forsta/securesms/sms/MessageSender.java +++ b/src/io/forsta/securesms/sms/MessageSender.java @@ -100,16 +100,11 @@ public static long send(final Context context, ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context); MmsDatabase database = DatabaseFactory.getMmsDatabase(context); - long allocatedThreadId; - if (threadId == -1) { - allocatedThreadId = threadDatabase.getThreadIdFor(message.getRecipients(), message.getDistributionType()); - } else { - allocatedThreadId = threadId; + throw new Exception("Invalid thread id"); } - Recipients recipients = message.getRecipients(); - long messageId = database.insertMessageOutbox(new MasterSecretUnion(masterSecret), message, allocatedThreadId, forceSms); + long messageId = database.insertMessageOutbox(new MasterSecretUnion(masterSecret), message, threadId, forceSms); sendMediaMessage(context, masterSecret, recipients, forceSms, messageId, message.getExpiresIn()); @@ -117,11 +112,13 @@ public static long send(final Context context, CcsmSync.syncMediaMessage(masterSecret, context, message); } - return allocatedThreadId; + return threadId; } catch (MmsException e) { Log.w(TAG, e); - return threadId; + } catch (Exception e) { + Log.w(TAG, "Message send exception: " + e); } + return threadId; } public static void resendGroupMessage(Context context, MasterSecret masterSecret, MessageRecord messageRecord, long filterRecipientId) { diff --git a/src/io/forsta/securesms/util/DirectoryHelper.java b/src/io/forsta/securesms/util/DirectoryHelper.java index b9cda1cc7..da60a639f 100644 --- a/src/io/forsta/securesms/util/DirectoryHelper.java +++ b/src/io/forsta/securesms/util/DirectoryHelper.java @@ -134,27 +134,27 @@ public static UserCapabilities refreshDirectoryFor(@NonNull Context context, @Nu ForstaServiceAccountManager accountManager = TextSecureCommunicationFactory.createManager(context); try { - List addresses = recipients.toNumberStringList(false); + List addresses = new ArrayList<>(); for (Recipient recipient : recipients) { if (TextUtils.isEmpty(recipient.getSlug())) { addresses.add(recipient.getNumber()); } } - String ids = TextUtils.join(",", addresses); - CcsmApi.syncForstaContacts(context, ids); - - List details = accountManager.getContacts(new HashSet(addresses)); - if (details.size() > 0) { - directory.setNumbers(details, new ArrayList()); - ContactDb contactsDb = DbFactory.getContactDb(context); - contactsDb.setActiveForstaAddresses(details); - return new UserCapabilities(Capability.SUPPORTED, Capability.UNSUPPORTED); + if (addresses.size() > 0) { + String ids = TextUtils.join(",", addresses); + CcsmApi.syncForstaContacts(context, ids); + + List details = accountManager.getContacts(new HashSet<>(addresses)); + if (details.size() > 0) { + directory.setNumbers(details, new ArrayList()); + ContactDb contactsDb = DbFactory.getContactDb(context); + contactsDb.setActiveForstaAddresses(details); + } } - return new UserCapabilities(Capability.UNSUPPORTED, Capability.UNSUPPORTED); } catch (IOException e) { e.printStackTrace(); } - return new UserCapabilities(Capability.UNSUPPORTED, Capability.UNSUPPORTED); + return new UserCapabilities(Capability.SUPPORTED, Capability.UNSUPPORTED); } public static void refreshDirectoryFor(Context context, MasterSecret masterSecret, List addresses) { diff --git a/src/io/forsta/securesms/util/IdentityUtil.java b/src/io/forsta/securesms/util/IdentityUtil.java index f0bf95db8..c05814493 100644 --- a/src/io/forsta/securesms/util/IdentityUtil.java +++ b/src/io/forsta/securesms/util/IdentityUtil.java @@ -16,6 +16,8 @@ import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.push.SignalServiceAddress; +import java.util.List; + public class IdentityUtil { @UiThread @@ -28,15 +30,16 @@ public static ListenableFuture> getRemoteIdentityKey(final new AsyncTask>() { @Override protected Optional doInBackground(Recipient... recipient) { - SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret); - SignalProtocolAddress axolotlAddress = new SignalProtocolAddress(recipient[0].getNumber(), SignalServiceAddress.DEFAULT_DEVICE_ID); - SessionRecord record = sessionStore.loadSession(axolotlAddress); - - if (record == null) { - return Optional.absent(); + SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret); + String addr = recipient[0].getNumber(); + List devices = sessionStore.getDeviceSessions(addr); + for (int device : devices) { + SessionRecord record = sessionStore.loadSession(new SignalProtocolAddress(addr, device)); + if (record != null) { + return Optional.fromNullable(record.getSessionState().getRemoteIdentityKey()); + } } - - return Optional.fromNullable(record.getSessionState().getRemoteIdentityKey()); + return Optional.absent(); } @Override diff --git a/src/io/forsta/securesms/util/TextSecurePreferences.java b/src/io/forsta/securesms/util/TextSecurePreferences.java index a5b3975ae..1813dae0a 100644 --- a/src/io/forsta/securesms/util/TextSecurePreferences.java +++ b/src/io/forsta/securesms/util/TextSecurePreferences.java @@ -98,6 +98,7 @@ public class TextSecurePreferences { public static final String SYSTEM_EMOJI_PREF = "pref_system_emoji"; private static final String MULTI_DEVICE_PROVISIONED_PREF = "pref_multi_device"; public static final String DIRECT_CAPTURE_CAMERA_ID = "pref_direct_capture_camera_id"; + public static final String LOCAL_DEVICE_ID = "pref_local_device_id"; public static void setDirectCaptureCameraId(Context context, int value) { setIntegerPrefrence(context, DIRECT_CAPTURE_CAMERA_ID, value); @@ -233,6 +234,14 @@ public static void setLocalNumber(Context context, String localNumber) { setStringPreference(context, LOCAL_NUMBER_PREF, localNumber); } + public static void setLocalDeviceID(Context context, Integer id) { + setIntegerPrefrence(context, LOCAL_DEVICE_ID, id); + } + + public static Integer getLocalDeviceId(Context context) { + return getIntegerPreference(context, LOCAL_DEVICE_ID, 1); + } + public static String getServer(Context context) { return getStringPreference(context, SERVER_PREF, null); }