diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/ProfileKeySendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/ProfileKeySendJob.java index c947dd7e5..2941e4505 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/ProfileKeySendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/ProfileKeySendJob.java @@ -65,16 +65,17 @@ public class ProfileKeySendJob extends BaseJob { if (queueLimits) { return new ProfileKeySendJob(new Parameters.Builder() - .setQueue(conversationRecipient.getId().toQueueKey()) + .setQueue("ProfileKeySendJob_" + conversationRecipient.getId().toQueueKey()) + .setMaxInstancesForQueue(1) .addConstraint(NetworkConstraint.KEY) + .addConstraint(DecryptionsDrainedConstraint.KEY) .setLifespan(TimeUnit.DAYS.toMillis(1)) .setMaxAttempts(Parameters.UNLIMITED) .build(), threadId, recipients); } else { return new ProfileKeySendJob(new Parameters.Builder() - .setQueue("ProfileKeySendJob_" + conversationRecipient.getId().toQueueKey()) + .setQueue(conversationRecipient.getId().toQueueKey()) .addConstraint(NetworkConstraint.KEY) - .addConstraint(DecryptionsDrainedConstraint.KEY) .setLifespan(TimeUnit.DAYS.toMillis(1)) .setMaxAttempts(Parameters.UNLIMITED) .build(), threadId, recipients); diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java index 129b2f204..251709700 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java @@ -285,7 +285,8 @@ public final class MessageContentProcessor { .enqueue(); } else if (!threadRecipient.isGroup()) { Log.i(TAG, "Message was to a 1:1. Ensuring this user has our profile key."); - ApplicationDependencies.getJobManager().startChain(new RefreshAttributesJob(false)) + ApplicationDependencies.getJobManager() + .startChain(new RefreshAttributesJob(false)) .then(ProfileKeySendJob.create(context, SignalDatabase.threads().getOrCreateThreadIdFor(threadRecipient), true)) .enqueue(); } @@ -1722,13 +1723,12 @@ public final class MessageContentProcessor { @NonNull byte[] messageProfileKeyBytes, @NonNull Recipient senderRecipient) { - log(content.getTimestamp(), "Profile key."); - RecipientDatabase database = SignalDatabase.recipients(); ProfileKey messageProfileKey = ProfileKeyUtil.profileKeyOrNull(messageProfileKeyBytes); if (messageProfileKey != null) { if (database.setProfileKey(senderRecipient.getId(), messageProfileKey)) { + log(content.getTimestamp(), "Profile key on message from " + senderRecipient.getId() + " didn't match our local store. It has been updated."); ApplicationDependencies.getJobManager().add(RetrieveProfileJob.forRecipient(senderRecipient.getId())); } } else { diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java index 73fc48e5b..782f294e2 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java @@ -1623,7 +1623,7 @@ public class SignalServiceMessageSender { if (!unidentifiedAccess.isPresent()) { try { SendMessageResponse response = new MessagingService.SendResponseProcessor<>(messagingService.send(messages, Optional.absent()).blockingGet()).getResultOrThrow(); - return SendMessageResult.success(recipient, messages.getDevices(), false, response.getNeedsSync() || store.isMultiDevice(), System.currentTimeMillis() - startTime, content.getContent()); + return SendMessageResult.success(recipient, messages.getDevices(), response.sentUnidentified(), response.getNeedsSync() || store.isMultiDevice(), System.currentTimeMillis() - startTime, content.getContent()); } catch (WebSocketUnavailableException e) { Log.i(TAG, "[sendMessage][" + timestamp + "] Pipe unavailable, falling back... (" + e.getClass().getSimpleName() + ": " + e.getMessage() + ")"); } catch (IOException e) { @@ -1633,7 +1633,7 @@ public class SignalServiceMessageSender { } else if (unidentifiedAccess.isPresent()) { try { SendMessageResponse response = new MessagingService.SendResponseProcessor<>(messagingService.send(messages, unidentifiedAccess).blockingGet()).getResultOrThrow(); - return SendMessageResult.success(recipient, messages.getDevices(), true, response.getNeedsSync() || store.isMultiDevice(), System.currentTimeMillis() - startTime, content.getContent()); + return SendMessageResult.success(recipient, messages.getDevices(), response.sentUnidentified(), response.getNeedsSync() || store.isMultiDevice(), System.currentTimeMillis() - startTime, content.getContent()); } catch (WebSocketUnavailableException e) { Log.i(TAG, "[sendMessage][" + timestamp + "] Unidentified pipe unavailable, falling back... (" + e.getClass().getSimpleName() + ": " + e.getMessage() + ")"); } catch (IOException e) { @@ -1648,7 +1648,7 @@ public class SignalServiceMessageSender { SendMessageResponse response = socket.sendMessage(messages, unidentifiedAccess); - return SendMessageResult.success(recipient, messages.getDevices(), unidentifiedAccess.isPresent(), response.getNeedsSync() || store.isMultiDevice(), System.currentTimeMillis() - startTime, content.getContent()); + return SendMessageResult.success(recipient, messages.getDevices(), response.sentUnidentified(), response.getNeedsSync() || store.isMultiDevice(), System.currentTimeMillis() - startTime, content.getContent()); } catch (InvalidKeyException ike) { Log.w(TAG, ike); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java index f98dcd880..11e7e63e6 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java @@ -36,6 +36,7 @@ import org.whispersystems.libsignal.SessionCipher; import org.whispersystems.libsignal.SignalProtocolAddress; import org.whispersystems.libsignal.UntrustedIdentityException; import org.whispersystems.libsignal.groups.GroupCipher; +import org.whispersystems.libsignal.logging.Log; import org.whispersystems.libsignal.protocol.CiphertextMessage; import org.whispersystems.libsignal.protocol.PreKeySignalMessage; import org.whispersystems.libsignal.protocol.SignalMessage; @@ -202,9 +203,15 @@ public class SignalServiceCipher { DecryptionResult result = sealedSessionCipher.decrypt(certificateValidator, ciphertext, envelope.getServerReceivedTimestamp()); SignalServiceAddress resultAddress = new SignalServiceAddress(ACI.parseOrThrow(result.getSenderUuid()), result.getSenderE164()); Optional groupId = result.getGroupId(); + boolean needsReceipt = true; + + if (envelope.hasSourceUuid()) { + Log.w(TAG, "[" + envelope.getTimestamp() + "] Received a UD-encrypted message sent over an identified channel. Marking as needsReceipt=false"); + needsReceipt = false; + } paddedMessage = result.getPaddedMessage(); - metadata = new SignalServiceMetadata(resultAddress, result.getDeviceId(), envelope.getTimestamp(), envelope.getServerReceivedTimestamp(), envelope.getServerDeliveredTimestamp(), true, envelope.getServerGuid(), groupId); + metadata = new SignalServiceMetadata(resultAddress, result.getDeviceId(), envelope.getTimestamp(), envelope.getServerReceivedTimestamp(), envelope.getServerDeliveredTimestamp(), needsReceipt, envelope.getServerGuid(), groupId); } else { throw new InvalidMetadataMessageException("Unknown type: " + envelope.getType()); } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/MessagingService.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/MessagingService.java index cc11b22f6..0655ba734 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/MessagingService.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/MessagingService.java @@ -2,6 +2,7 @@ package org.whispersystems.signalservice.api.services; import com.google.protobuf.ByteString; +import org.whispersystems.libsignal.logging.Log; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalWebSocket; import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess; @@ -52,9 +53,11 @@ public class MessagingService { .build(); ResponseMapper responseMapper = DefaultResponseMapper.extend(SendMessageResponse.class) - .withResponseMapper((status, body, getHeader) -> { - SendMessageResponse sendMessageResponse = Util.isEmpty(body) ? new SendMessageResponse(false) + .withResponseMapper((status, body, getHeader, unidentified) -> { + SendMessageResponse sendMessageResponse = Util.isEmpty(body) ? new SendMessageResponse(false, unidentified) : JsonUtil.fromJsonResponse(body, SendMessageResponse.class); + sendMessageResponse.setSentUnidentfied(unidentified); + return ServiceResponse.forResult(sendMessageResponse, status, body); }) .withCustomError(404, (status, body, getHeader) -> new UnregisteredUserException(list.getDestination(), new NotFoundException("not found"))) diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/ProfileService.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/ProfileService.java index b8384ba74..4586c953c 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/ProfileService.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/ProfileService.java @@ -125,7 +125,7 @@ public final class ProfileService { } @Override - public ServiceResponse map(int status, String body, Function getHeader) + public ServiceResponse map(int status, String body, Function getHeader, boolean unidentified) throws MalformedResponseException { try { diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/ServiceResponse.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/ServiceResponse.java index d82af3ccd..c73763c9f 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/ServiceResponse.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/ServiceResponse.java @@ -11,7 +11,7 @@ import java.util.concurrent.ExecutionException; import io.reactivex.rxjava3.core.Single; /** - * Encapsulates a parsed APi response regardless of where it came from (WebSocket or REST). Not only + * Encapsulates a parsed API response regardless of where it came from (WebSocket or REST). Not only * includes the success result but also any application errors encountered (404s, parsing, etc.) or * execution errors encountered (IOException, etc.). */ diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java index e2fc6fbdf..a6d0aa405 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java @@ -506,7 +506,7 @@ public class PushServiceSocket { try { String responseText = makeServiceRequest(String.format(MESSAGE_PATH, bundle.getDestination()), "PUT", JsonUtil.toJson(bundle), NO_HEADERS, unidentifiedAccess); - if (responseText == null) return new SendMessageResponse(false); + if (responseText == null) return new SendMessageResponse(false, unidentifiedAccess.isPresent()); else return JsonUtil.fromJson(responseText, SendMessageResponse.class); } catch (NotFoundException nfe) { throw new UnregisteredUserException(bundle.getDestination(), nfe); @@ -517,7 +517,7 @@ public class PushServiceSocket { ListenableFuture response = submitServiceRequest(String.format(MESSAGE_PATH, bundle.getDestination()), "PUT", JsonUtil.toJson(bundle), NO_HEADERS, unidentifiedAccess); return FutureTransformers.map(response, body -> { - return body == null ? new SendMessageResponse(false) + return body == null ? new SendMessageResponse(false, unidentifiedAccess.isPresent()) : JsonUtil.fromJson(body, SendMessageResponse.class); }); } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/SendMessageResponse.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/SendMessageResponse.java index ac129f271..36457ceb4 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/SendMessageResponse.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/SendMessageResponse.java @@ -1,16 +1,30 @@ package org.whispersystems.signalservice.internal.push; +import com.fasterxml.jackson.annotation.JsonProperty; + public class SendMessageResponse { + @JsonProperty private boolean needsSync; + private boolean sentUnidentfied; + public SendMessageResponse() {} - public SendMessageResponse(boolean needsSync) { - this.needsSync = needsSync; + public SendMessageResponse(boolean needsSync, boolean sentUnidentified) { + this.needsSync = needsSync; + this.sentUnidentfied = sentUnidentified; } public boolean getNeedsSync() { return needsSync; } + + public boolean sentUnidentified() { + return sentUnidentfied; + } + + public void setSentUnidentfied(boolean value) { + this.sentUnidentfied = value; + } } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/DefaultResponseMapper.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/DefaultResponseMapper.java index e622be739..3b39dd20e 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/DefaultResponseMapper.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/DefaultResponseMapper.java @@ -41,12 +41,12 @@ public class DefaultResponseMapper implements ResponseMapper } @Override - public ServiceResponse map(int status, String body, Function getHeader) { + public ServiceResponse map(int status, String body, Function getHeader, boolean unidentified) { Throwable applicationError = errorMapper.parseError(status, body, getHeader); if (applicationError == null) { try { if (customResponseMapper != null) { - return Objects.requireNonNull(customResponseMapper.map(status, body, getHeader)); + return Objects.requireNonNull(customResponseMapper.map(status, body, getHeader, unidentified)); } return ServiceResponse.forResult(JsonUtil.fromJsonResponse(body, clazz), status, body); } catch (MalformedResponseException e) { @@ -81,6 +81,6 @@ public class DefaultResponseMapper implements ResponseMapper } public interface CustomResponseMapper { - ServiceResponse map(int status, String body, Function getHeader) throws MalformedResponseException; + ServiceResponse map(int status, String body, Function getHeader, boolean unidentified) throws MalformedResponseException; } } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/ResponseMapper.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/ResponseMapper.java index 0b202ae65..1ef4c2307 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/ResponseMapper.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/ResponseMapper.java @@ -15,9 +15,9 @@ import org.whispersystems.signalservice.internal.ServiceResponse; * @param - The final type the API response will map into. */ public interface ResponseMapper { - ServiceResponse map(int status, String body, Function getHeader); + ServiceResponse map(int status, String body, Function getHeader, boolean unidentified); default ServiceResponse map(WebsocketResponse response) { - return map(response.getStatus(), response.getBody(), response::getHeader); + return map(response.getStatus(), response.getBody(), response::getHeader, response.isUnidentified()); } } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/WebSocketConnection.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/WebSocketConnection.java index 61df211a0..ad16efd19 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/WebSocketConnection.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/WebSocketConnection.java @@ -266,7 +266,8 @@ public class WebSocketConnection extends WebSocketListener { if (listener != null) { listener.onSuccess(new WebsocketResponse(message.getResponse().getStatus(), new String(message.getResponse().getBody().toByteArray()), - message.getResponse().getHeadersList())); + message.getResponse().getHeadersList(), + !credentialsProvider.isPresent())); if (message.getResponse().getStatus() >= 400) { healthMonitor.onMessageError(message.getResponse().getStatus(), credentialsProvider.isPresent()); } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/WebsocketResponse.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/WebsocketResponse.java index 3ee0282f6..9169ff613 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/WebsocketResponse.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/WebsocketResponse.java @@ -10,11 +10,13 @@ public class WebsocketResponse { private final int status; private final String body; private final Map headers; + private final boolean unidentified; - WebsocketResponse(int status, String body, List headers) { - this.status = status; - this.body = body; - this.headers = parseHeaders(headers); + WebsocketResponse(int status, String body, List headers, boolean unidentified) { + this.status = status; + this.body = body; + this.headers = parseHeaders(headers); + this.unidentified = unidentified; } public int getStatus() { @@ -29,6 +31,10 @@ public class WebsocketResponse { return headers.get(Preconditions.checkNotNull(key.toLowerCase())); } + public boolean isUnidentified() { + return unidentified; + } + private static Map parseHeaders(List rawHeaders) { Map headers = new HashMap<>(rawHeaders.size());