async Task DecryptDownloadedMessage(XMessage xMessage) { Message decryptedMessage = await TryDecryptMessageToFindSenderEnryptDecryptionKeyAndSaveIt(xMessage); if (decryptedMessage == null) // TODO: we'll be here also when Ratchet mismatch error is happening. Then, the message should not be just dropped. Do we have the sender's Id so that we can notify him? { return; // Ignore the message - it was truely garbled or a resend request was already queued. In both cases we are done with it here. } if (decryptedMessage.MessageType.IsReceipt()) { Message messageToConfirm = await this.repo.GetMessage(decryptedMessage.SenderLocalMessageId, decryptedMessage.SenderId); if (messageToConfirm != null) // the message could have already been deleted { messageToConfirm.SendMessageState = decryptedMessage.MessageType == MessageType.DeliveryReceipt ? SendMessageState.Delivered : SendMessageState.Read; await this.repo.UpdateMessage(messageToConfirm); SendMessageStateUpdated?.Invoke(this, messageToConfirm); } return; } if (decryptedMessage.MessageType.IsContent()) { if (this.contactListManager.CurrentContact == null || this.contactListManager.CurrentContact.Id != decryptedMessage.SenderId) { await SendReceipt(decryptedMessage, MessageType.DeliveryReceipt); // This message will become an UNREAD message and not be displayed await this.contactListManager.HandelUnreadAndChatPreviewForIncomingMessageFromChatWorker(decryptedMessage, countAsUnread : true); } else { await SendReceipt(decryptedMessage, MessageType.ReadReceipt); // this message will become an READ message and display immediately await this.contactListManager.HandelUnreadAndChatPreviewForIncomingMessageFromChatWorker(decryptedMessage, countAsUnread : false); IncomingMessageDecrypted?.Invoke(this, decryptedMessage); } } #region Incoming Contact Logic // Check if the sender is in our contacts // OLD: Identity contactInList = existingContacts.SingleOrDefault(c => c.ChatId == msg.SenderId); /* * Identity contactInList = existingContacts.SingleOrDefault(c => c.ChatId == msg.SenderId); * if (contactInList != null && contactInList.ContactState == ContactState.Valid) * continue; // Yes, proceed checking the nex message * * if (contactInList != null && contactInList.ContactState == ContactState.Added) * { * await VerifyContactInAddedState(contactInList); * * // Check if the contact is valid now * existingContacts = await repo.GetAllContacts(); * contactInList = existingContacts.SingleOrDefault(c => c.ChatId == msg.SenderId); * if (contactInList.ContactState == ContactState.Valid) * continue; // Yes, proceed checking the nex message * * // If still not valid, drop the message * msg.MessageType = MessageType.None; // MessageType.None will be ignored by the code below * continue; // Proceed checking the nex message * } * var incomingContact = new Identity { Id = Guid.NewGuid().ToString(), UnverifiedId=msg.SenderId, ContactState = ContactState.Added, Name = "Incoming Contact" }; * await repo.AddContact(incomingContact); * await VerifyContactInAddedState(incomingContact); * * // Check if the incoming contact is valid now * existingContacts = await repo.GetAllContacts(); * contactInList = existingContacts.SingleOrDefault(c => c.ChatId == msg.SenderId); * if (contactInList.ContactState == ContactState.Valid) * continue; // Yes, proceed checking the next message * * // If still not valid, drop the message * msg.MessageType = MessageType.None; // MessageType.None will be ignored by the code below */ #endregion }
async Task CheckForResendRequestsAsync() { // 1. Get the hashes of messages where we did not get a delivery or read receipt var contacts = await this.repo.GetAllContacts(); foreach (var c in contacts.Where(c => c.ContactState == ContactState.Valid).ToArray()) { // First, look at the last message and try to detect cases where simply everything is ok var lastMessage = await this.repo.GetLastMessage(c.Id); if (lastMessage == null || // if there is no message, there is nothing to resend lastMessage.Side == MessageSide.You || // if the last message was incoming, it means the other side has the keys, it's ok lastMessage.SendMessageState == SendMessageState.Delivered || lastMessage.SendMessageState == SendMessageState.Read) // also nothing to repair { continue; } // Now let's look deeper. We may have sent a couple of messages, that are all still without receipts. That means our state is XDSNetwork. // It's possible they could't be read, and the other side has already posted resend requests. // Of course it's also possible the other side has just been offline though. Unfortunately, we don't know. var moreLastMessages = await this.repo.GetMessageRange(0, 10, c.Id); foreach (var message in moreLastMessages) { if (message.Side == MessageSide.Me && message.SendMessageState == SendMessageState.XDSNetwork && message.SendMessageState != SendMessageState.Resent && DateTime.UtcNow - message.GetEncrytedDateUtc() > TimeSpan.FromSeconds(60) && // old enough to check DateTime.UtcNow - message.GetEncrytedDateUtc() < TimeSpan.FromDays(7)) // young enough not to be expired { var resendRequestQuery = new XResendRequest { Id = message.NetworkPayloadHash, RecipientId = c.ChatId }; var response = await this.chatClient.CheckForResendRequest(resendRequestQuery); // check if resend request has been posted, or if the sent message is still there and has not yet been fetched. if (!response.IsSuccess) { return; // give up the whole procedure for all contacts, something else is broken } if (response.Result == 0) { // There is nothing on the server. Not the original message and no resend request. Possible causes: // - The recipient did download it but did not send a resend request, or a receipt yet. // - The message or receipt or resend request was lost. // => keep checking. It's possible a resend request or receipt arrives, it drops out the top 10 we are checking // or max ago for checking after messages arrives, so that we stop checking eventually. // => but do not resend the message. // => but do check the other messages of this contact. message.SendMessageState = SendMessageState.Untracable; await this.repo.UpdateMessage(message); SendMessageStateUpdated?.Invoke(this, message); continue; } if (response.Result == 1) { // The original message is still on the server. // - The recipient hasn't downloaded his messages. // => stop checking this contact break; } if (response.Result == 2) { // yes, there is a resend request on the server. // => resend the message await this.chatEncryptionService.DecryptCipherTextInVisibleBubble(message); await this.chatEncryptionService.EncryptMessage(message, true); var resendResponse = await this.chatClient.UploadMessage(message); if (resendResponse.IsSuccess) { message.SendMessageState = SendMessageState.Resent; await this.repo.UpdateMessage(message); SendMessageStateUpdated?.Invoke(this, message); } } } } } }