use of org.thoughtcrime.securesms.groups.GroupId in project Signal-Android by WhisperSystems.
the class MessageContentProcessor method updateGroupReceiptStatus.
private void updateGroupReceiptStatus(@NonNull SentTranscriptMessage message, long messageId, @NonNull GroupId groupString) {
GroupReceiptDatabase receiptDatabase = SignalDatabase.groupReceipts();
List<RecipientId> messageRecipientIds = Stream.of(message.getRecipients()).map(RecipientId::from).toList();
List<Recipient> members = SignalDatabase.groups().getGroupMembers(groupString, GroupDatabase.MemberSet.FULL_MEMBERS_EXCLUDING_SELF);
Map<RecipientId, Integer> localReceipts = Stream.of(receiptDatabase.getGroupReceiptInfo(messageId)).collect(Collectors.toMap(GroupReceiptInfo::getRecipientId, GroupReceiptInfo::getStatus));
for (RecipientId messageRecipientId : messageRecipientIds) {
// noinspection ConstantConditions
if (localReceipts.containsKey(messageRecipientId) && localReceipts.get(messageRecipientId) < GroupReceiptDatabase.STATUS_UNDELIVERED) {
receiptDatabase.update(messageRecipientId, messageId, GroupReceiptDatabase.STATUS_UNDELIVERED, message.getTimestamp());
} else if (!localReceipts.containsKey(messageRecipientId)) {
receiptDatabase.insert(Collections.singletonList(messageRecipientId), messageId, GroupReceiptDatabase.STATUS_UNDELIVERED, message.getTimestamp());
}
}
List<org.whispersystems.libsignal.util.Pair<RecipientId, Boolean>> unidentifiedStatus = Stream.of(members).map(m -> new org.whispersystems.libsignal.util.Pair<>(m.getId(), message.isUnidentified(m.requireServiceId().toString()))).toList();
receiptDatabase.setUnidentified(unidentifiedStatus, messageId);
}
use of org.thoughtcrime.securesms.groups.GroupId in project Signal-Android by WhisperSystems.
the class MessageContentProcessor method handleSenderKeyRetryReceipt.
private void handleSenderKeyRetryReceipt(@NonNull Recipient requester, @Nullable MessageLogEntry messageLogEntry, @NonNull SignalServiceContent content, @NonNull DecryptionErrorMessage decryptionErrorMessage) {
long sentTimestamp = decryptionErrorMessage.getTimestamp();
MessageRecord relatedMessage = findRetryReceiptRelatedMessage(context, messageLogEntry, sentTimestamp);
if (relatedMessage == null) {
warn(content.getTimestamp(), "[RetryReceipt-SK] The related message could not be found! There shouldn't be any sender key resends where we can't find the related message. Skipping.");
return;
}
Recipient threadRecipient = SignalDatabase.threads().getRecipientForThreadId(relatedMessage.getThreadId());
if (threadRecipient == null) {
warn(content.getTimestamp(), "[RetryReceipt-SK] Could not find a thread recipient! Skipping.");
return;
}
if (!threadRecipient.isPushV2Group()) {
warn(content.getTimestamp(), "[RetryReceipt-SK] Thread recipient is not a v2 group! Skipping.");
return;
}
GroupId.V2 groupId = threadRecipient.requireGroupId().requireV2();
DistributionId distributionId = SignalDatabase.groups().getOrCreateDistributionId(groupId);
SignalProtocolAddress requesterAddress = new SignalProtocolAddress(requester.requireServiceId().toString(), content.getSenderDevice());
SignalDatabase.senderKeyShared().delete(distributionId, Collections.singleton(requesterAddress));
if (messageLogEntry != null) {
warn(content.getTimestamp(), "[RetryReceipt-SK] Found MSL entry for " + requester.getId() + " (" + requesterAddress + ") with timestamp " + sentTimestamp + ". Scheduling a resend.");
ApplicationDependencies.getJobManager().add(new ResendMessageJob(messageLogEntry.getRecipientId(), messageLogEntry.getDateSent(), messageLogEntry.getContent(), messageLogEntry.getContentHint(), groupId, distributionId));
} else {
warn(content.getTimestamp(), "[RetryReceipt-SK] Unable to find MSL entry for " + requester.getId() + " (" + requesterAddress + ") with timestamp " + sentTimestamp + ".");
Optional<GroupRecord> groupRecord = SignalDatabase.groups().getGroup(groupId);
if (!groupRecord.isPresent()) {
warn(content.getTimestamp(), "[RetryReceipt-SK] Could not find a record for the group!");
return;
}
if (!groupRecord.get().getMembers().contains(requester.getId())) {
warn(content.getTimestamp(), "[RetryReceipt-SK] The requester is not in the group, so we cannot send them a SenderKeyDistributionMessage.");
return;
}
warn(content.getTimestamp(), "[RetryReceipt-SK] The requester is in the group, so we'll send them a SenderKeyDistributionMessage.");
ApplicationDependencies.getJobManager().add(new SenderKeyDistributionSendJob(requester.getId(), groupRecord.get().getId().requireV2()));
}
}
use of org.thoughtcrime.securesms.groups.GroupId in project Signal-Android by WhisperSystems.
the class MessageContentProcessor method handleMessage.
private void handleMessage(@NonNull SignalServiceContent content, long timestamp, @NonNull Recipient senderRecipient, @NonNull Optional<Long> smsMessageId) throws IOException, GroupChangeBusyException {
try {
Recipient threadRecipient = getMessageDestination(content);
if (shouldIgnore(content, senderRecipient, threadRecipient)) {
log(content.getTimestamp(), "Ignoring message.");
return;
}
PendingRetryReceiptModel pending = ApplicationDependencies.getPendingRetryReceiptCache().get(senderRecipient.getId(), content.getTimestamp());
long receivedTime = handlePendingRetry(pending, content, threadRecipient);
log(String.valueOf(content.getTimestamp()), "Beginning message processing. Sender: " + formatSender(senderRecipient, content));
if (content.getDataMessage().isPresent()) {
GroupDatabase groupDatabase = SignalDatabase.groups();
SignalServiceDataMessage message = content.getDataMessage().get();
boolean isMediaMessage = message.getAttachments().isPresent() || message.getQuote().isPresent() || message.getSharedContacts().isPresent() || message.getPreviews().isPresent() || message.getSticker().isPresent() || message.getMentions().isPresent();
Optional<GroupId> groupId = GroupUtil.idFromGroupContext(message.getGroupContext());
boolean isGv2Message = groupId.isPresent() && groupId.get().isV2();
if (isGv2Message) {
if (handleGv2PreProcessing(groupId.orNull().requireV2(), content, content.getDataMessage().get().getGroupContext().get().getGroupV2().get(), senderRecipient)) {
return;
}
}
MessageId messageId = null;
if (isInvalidMessage(message))
handleInvalidMessage(content.getSender(), content.getSenderDevice(), groupId, content.getTimestamp(), smsMessageId);
else if (message.isEndSession())
messageId = handleEndSessionMessage(content, smsMessageId, senderRecipient);
else if (message.isGroupV1Update())
handleGroupV1Message(content, message, smsMessageId, groupId.get().requireV1(), senderRecipient, threadRecipient, receivedTime);
else if (message.isExpirationUpdate())
messageId = handleExpirationUpdate(content, message, smsMessageId, groupId, senderRecipient, threadRecipient, receivedTime);
else if (message.getReaction().isPresent())
messageId = handleReaction(content, message, senderRecipient);
else if (message.getRemoteDelete().isPresent())
messageId = handleRemoteDelete(content, message, senderRecipient);
else if (message.getPayment().isPresent())
handlePayment(content, message, senderRecipient);
else if (message.getStoryContext().isPresent())
handleStoryMessage(content);
else if (isMediaMessage)
messageId = handleMediaMessage(content, message, smsMessageId, senderRecipient, threadRecipient, receivedTime);
else if (message.getBody().isPresent())
messageId = handleTextMessage(content, message, smsMessageId, groupId, senderRecipient, threadRecipient, receivedTime);
else if (Build.VERSION.SDK_INT > 19 && message.getGroupCallUpdate().isPresent())
handleGroupCallUpdateMessage(content, message, groupId, senderRecipient);
if (groupId.isPresent() && groupDatabase.isUnknownGroup(groupId.get())) {
handleUnknownGroupMessage(content, message.getGroupContext().get(), senderRecipient);
}
if (message.getProfileKey().isPresent()) {
handleProfileKey(content, message.getProfileKey().get(), senderRecipient);
}
if (content.isNeedsReceipt() && messageId != null) {
handleNeedsDeliveryReceipt(content, message, messageId);
} else if (!content.isNeedsReceipt()) {
if (RecipientUtil.shouldHaveProfileKey(context, threadRecipient)) {
Log.w(TAG, "Received an unsealed sender message from " + senderRecipient.getId() + ", but they should already have our profile key. Correcting.");
if (groupId.isPresent() && groupId.get().isV2()) {
Log.i(TAG, "Message was to a GV2 group. Ensuring our group profile keys are up to date.");
ApplicationDependencies.getJobManager().startChain(new RefreshAttributesJob(false)).then(GroupV2UpdateSelfProfileKeyJob.withQueueLimits(groupId.get().requireV2())).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)).then(ProfileKeySendJob.create(context, SignalDatabase.threads().getOrCreateThreadIdFor(threadRecipient), true)).enqueue();
}
}
}
} else if (content.getSyncMessage().isPresent()) {
TextSecurePreferences.setMultiDevice(context, true);
SignalServiceSyncMessage syncMessage = content.getSyncMessage().get();
if (syncMessage.getSent().isPresent())
handleSynchronizeSentMessage(content, syncMessage.getSent().get(), senderRecipient);
else if (syncMessage.getRequest().isPresent())
handleSynchronizeRequestMessage(syncMessage.getRequest().get(), content.getTimestamp());
else if (syncMessage.getRead().isPresent())
handleSynchronizeReadMessage(syncMessage.getRead().get(), content.getTimestamp(), senderRecipient);
else if (syncMessage.getViewed().isPresent())
handleSynchronizeViewedMessage(syncMessage.getViewed().get(), content.getTimestamp());
else if (syncMessage.getViewOnceOpen().isPresent())
handleSynchronizeViewOnceOpenMessage(syncMessage.getViewOnceOpen().get(), content.getTimestamp());
else if (syncMessage.getVerified().isPresent())
handleSynchronizeVerifiedMessage(syncMessage.getVerified().get());
else if (syncMessage.getStickerPackOperations().isPresent())
handleSynchronizeStickerPackOperation(syncMessage.getStickerPackOperations().get(), content.getTimestamp());
else if (syncMessage.getConfiguration().isPresent())
handleSynchronizeConfigurationMessage(syncMessage.getConfiguration().get(), content.getTimestamp());
else if (syncMessage.getBlockedList().isPresent())
handleSynchronizeBlockedListMessage(syncMessage.getBlockedList().get());
else if (syncMessage.getFetchType().isPresent())
handleSynchronizeFetchMessage(syncMessage.getFetchType().get(), content.getTimestamp());
else if (syncMessage.getMessageRequestResponse().isPresent())
handleSynchronizeMessageRequestResponse(syncMessage.getMessageRequestResponse().get(), content.getTimestamp());
else if (syncMessage.getOutgoingPaymentMessage().isPresent())
handleSynchronizeOutgoingPayment(content, syncMessage.getOutgoingPaymentMessage().get());
else if (syncMessage.getKeys().isPresent())
handleSynchronizeKeys(syncMessage.getKeys().get(), content.getTimestamp());
else if (syncMessage.getContacts().isPresent())
handleSynchronizeContacts(syncMessage.getContacts().get(), content.getTimestamp());
else
warn(String.valueOf(content.getTimestamp()), "Contains no known sync types...");
} else if (content.getCallMessage().isPresent()) {
log(String.valueOf(content.getTimestamp()), "Got call message...");
SignalServiceCallMessage message = content.getCallMessage().get();
Optional<Integer> destinationDeviceId = message.getDestinationDeviceId();
if (destinationDeviceId.isPresent() && destinationDeviceId.get() != SignalStore.account().getDeviceId()) {
log(String.valueOf(content.getTimestamp()), String.format(Locale.US, "Ignoring call message that is not for this device! intended: %d, this: %d", destinationDeviceId.get(), SignalStore.account().getDeviceId()));
return;
}
if (message.getOfferMessage().isPresent())
handleCallOfferMessage(content, message.getOfferMessage().get(), smsMessageId, senderRecipient);
else if (message.getAnswerMessage().isPresent())
handleCallAnswerMessage(content, message.getAnswerMessage().get(), senderRecipient);
else if (message.getIceUpdateMessages().isPresent())
handleCallIceUpdateMessage(content, message.getIceUpdateMessages().get(), senderRecipient);
else if (message.getHangupMessage().isPresent())
handleCallHangupMessage(content, message.getHangupMessage().get(), smsMessageId, senderRecipient);
else if (message.getBusyMessage().isPresent())
handleCallBusyMessage(content, message.getBusyMessage().get(), senderRecipient);
else if (message.getOpaqueMessage().isPresent())
handleCallOpaqueMessage(content, message.getOpaqueMessage().get(), senderRecipient);
} else if (content.getReceiptMessage().isPresent()) {
SignalServiceReceiptMessage message = content.getReceiptMessage().get();
if (message.isReadReceipt())
handleReadReceipt(content, message, senderRecipient);
else if (message.isDeliveryReceipt())
handleDeliveryReceipt(content, message, senderRecipient);
else if (message.isViewedReceipt())
handleViewedReceipt(content, message, senderRecipient);
} else if (content.getTypingMessage().isPresent()) {
handleTypingMessage(content, content.getTypingMessage().get(), senderRecipient);
} else if (content.getDecryptionErrorMessage().isPresent()) {
handleRetryReceipt(content, content.getDecryptionErrorMessage().get(), senderRecipient);
} else if (content.getSenderKeyDistributionMessage().isPresent()) {
// Already handled, here in order to prevent unrecognized message log
} else {
warn(String.valueOf(content.getTimestamp()), "Got unrecognized message!");
}
resetRecipientToPush(senderRecipient);
if (pending != null) {
warn(content.getTimestamp(), "Pending retry was processed. Deleting.");
ApplicationDependencies.getPendingRetryReceiptCache().delete(pending);
}
} catch (StorageFailedException e) {
warn(String.valueOf(content.getTimestamp()), e);
handleCorruptMessage(e.getSender(), e.getSenderDevice(), timestamp, smsMessageId);
} catch (BadGroupIdException e) {
warn(String.valueOf(content.getTimestamp()), "Ignoring message with bad group id", e);
}
}
use of org.thoughtcrime.securesms.groups.GroupId in project Signal-Android by WhisperSystems.
the class MessageDecryptionUtil method handleRetry.
@NonNull
private static Job handleRetry(@NonNull Context context, @NonNull Recipient sender, @NonNull SignalServiceEnvelope envelope, @NonNull ProtocolException protocolException) {
ContentHint contentHint = ContentHint.fromType(protocolException.getContentHint());
int senderDevice = protocolException.getSenderDevice();
long receivedTimestamp = System.currentTimeMillis();
Optional<GroupId> groupId = Optional.absent();
if (protocolException.getGroupId().isPresent()) {
try {
groupId = Optional.of(GroupId.push(protocolException.getGroupId().get()));
} catch (BadGroupIdException e) {
Log.w(TAG, "[" + envelope.getTimestamp() + "] Bad groupId!");
}
}
Log.w(TAG, "[" + envelope.getTimestamp() + "] Could not decrypt a message with a type of " + contentHint);
long threadId;
if (groupId.isPresent()) {
Recipient groupRecipient = Recipient.externalPossiblyMigratedGroup(context, groupId.get());
threadId = SignalDatabase.threads().getOrCreateThreadIdFor(groupRecipient);
} else {
threadId = SignalDatabase.threads().getOrCreateThreadIdFor(sender);
}
switch(contentHint) {
case DEFAULT:
Log.w(TAG, "[" + envelope.getTimestamp() + "] Inserting an error right away because it's " + contentHint);
SignalDatabase.sms().insertBadDecryptMessage(sender.getId(), senderDevice, envelope.getTimestamp(), receivedTimestamp, threadId);
break;
case RESENDABLE:
Log.w(TAG, "[" + envelope.getTimestamp() + "] Inserting into pending retries store because it's " + contentHint);
ApplicationDependencies.getPendingRetryReceiptCache().insert(sender.getId(), senderDevice, envelope.getTimestamp(), receivedTimestamp, threadId);
ApplicationDependencies.getPendingRetryReceiptManager().scheduleIfNecessary();
break;
case IMPLICIT:
Log.w(TAG, "[" + envelope.getTimestamp() + "] Not inserting any error because it's " + contentHint);
break;
}
byte[] originalContent;
int envelopeType;
if (protocolException.getUnidentifiedSenderMessageContent().isPresent()) {
originalContent = protocolException.getUnidentifiedSenderMessageContent().get().getContent();
envelopeType = protocolException.getUnidentifiedSenderMessageContent().get().getType();
} else {
originalContent = envelope.getContent();
envelopeType = envelopeTypeToCiphertextMessageType(envelope.getType());
}
DecryptionErrorMessage decryptionErrorMessage = DecryptionErrorMessage.forOriginalMessage(originalContent, envelopeType, envelope.getTimestamp(), senderDevice);
return new SendRetryReceiptJob(sender.getId(), groupId, decryptionErrorMessage);
}
use of org.thoughtcrime.securesms.groups.GroupId in project Signal-Android by WhisperSystems.
the class SenderKeyDistributionSendJob method onRun.
@Override
protected void onRun() throws Exception {
GroupDatabase groupDatabase = SignalDatabase.groups();
if (!groupDatabase.isCurrentMember(groupId, recipientId)) {
Log.w(TAG, recipientId + " is no longer a member of " + groupId + "! Not sending.");
return;
}
Recipient recipient = Recipient.resolved(recipientId);
if (recipient.getSenderKeyCapability() != Recipient.Capability.SUPPORTED) {
Log.w(TAG, recipientId + " does not support sender key! Not sending.");
return;
}
if (recipient.isUnregistered()) {
Log.w(TAG, recipient.getId() + " not registered!");
return;
}
SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender();
List<SignalServiceAddress> address = Collections.singletonList(RecipientUtil.toSignalServiceAddress(context, recipient));
DistributionId distributionId = groupDatabase.getOrCreateDistributionId(groupId);
SenderKeyDistributionMessage message = messageSender.getOrCreateNewGroupSession(distributionId);
List<Optional<UnidentifiedAccessPair>> access = UnidentifiedAccessUtil.getAccessFor(context, Collections.singletonList(recipient));
SendMessageResult result = messageSender.sendSenderKeyDistributionMessage(distributionId, address, access, message, groupId.getDecodedId()).get(0);
if (result.isSuccess()) {
List<SignalProtocolAddress> addresses = result.getSuccess().getDevices().stream().map(device -> recipient.requireServiceId().toProtocolAddress(device)).collect(Collectors.toList());
ApplicationDependencies.getProtocolStore().aci().markSenderKeySharedWith(distributionId, addresses);
}
}
Aggregations