Exemple #1
0
 public static byte[] Serialize(this XResendRequest r)
 {
     byte[] serialized = PocoSerializer.Begin()
                         .Append(r.Id)
                         .Append(r.RecipientId)
                         .Finish();
     return(serialized);
 }
Exemple #2
0
        public static XResendRequest DeserializeResendRequest(this byte[] resendRequest)
        {
            var r = new XResendRequest();

            var ser = PocoSerializer.GetDeserializer(resendRequest);

            r.Id          = ser.MakeString(0);
            r.RecipientId = ser.MakeString(1);
            return(r);
        }
        public async Task <string> AddResendRequest(XResendRequest resendRequest)
        {
            await SemaphoreSlim.WaitAsync();

            try
            {
                await this.resendRequestsRepository.Add(resendRequest);

                return($"{resendRequest.Id}");
            }
            finally
            {
                SemaphoreSlim.Release();
            }
        }
        public async Task <byte> CheckForResendRequest(XResendRequest resendRequestQuery)
        {
            await SemaphoreSlim.WaitAsync();

            try
            {
                // 1.) Check if the message is still stored here
                XMessage message = await this.messagesRepository.Get(resendRequestQuery.Id, resendRequestQuery.RecipientId);

                XResendRequest resendRequest = await this.resendRequestsRepository.Get(resendRequestQuery.Id);

                // Since the client is checking for a resend request because he still sees 'XDSNetwork',
                // this means normally that the receiver has downloaded the message, has not send a resend request (yet),
                // and the sender also has not got a receipt yet (if he had, he would not be checking here).
                // When 0 is returned, the client sets the state to SendMessageState.Untracable and keeps checking till a timeout is reached.
                if (message == null && resendRequest == null)
                {
                    return(0);
                }

                // Ultimately it will probably be the superior solution _not_ to delete messages after one download, especially in the decentralised scenario. Deleting would need
                // cryptographic authentication by the receiver (less privacy) and the event would need to be propagated across the network. Rather, the messages could simply expire.
                // That would also enable the use of more than one device for one chat id (the message is not gone if the other device pulls it first) and in the group case
                // we also cannot delete message until the last member has got it.
                if (message != null && resendRequest != null)
                {
                    throw new InvalidOperationException("Weird! If the message is still there, there should be no ResendRequest, or didn't we delete the message when downloading it?");
                }

                // The message is still there, waiting to be downloaded.
                // Querying client will keep SendMessageState.OnServer and keep checking.
                if (message != null)
                {
                    return(1);
                }

                return(2);       // There is no message but a ResendRequest fwas found. Yes, resend required. Client will resend and change state to SendMessageState.Resent. Will not check or resend any more.
            }
            finally
            {
                SemaphoreSlim.Release();
            }
        }
        public async Task <Response <byte> > CheckForResendRequest(XResendRequest resendRequest)
        {
            var response = new Response <byte>();

            try
            {
                var requestCommand = new RequestCommand(CommandId.CheckForResendRequest, resendRequest).Serialize(CommandHeader.Yes);
                var tlsResponse    = await this.networkClient.SendRequestAsync(requestCommand, Transport.TCP);

                AssertOneItem(tlsResponse);

                var contents = tlsResponse[0].CommandData[0];
                response.Result = contents;
                response.SetSuccess();
            }
            catch (Exception e)
            {
                this.logger.LogError(e.Message);
                response.SetError(e);
            }
            return(response);
        }
        /// <inheritdoc />
        /// <summary>
        /// Uploads a resend request.
        /// </summary>
        /// <param name="resendRequest">The XResendRequest containing only the NetworkPayloadHash of the message that the other party should resend.</param>
        /// <returns>The NetworkPayloadHash GuidString, to indicate success.</returns>
        public async Task <Response <NetworkPayloadAdded> > UploadResendRequest(XResendRequest resendRequest)
        {
            var response = new Response <NetworkPayloadAdded>();

            try
            {
                this.logger.LogDebug($"Uploading resendRequest: NPH/ID: {resendRequest.Id}, {resendRequest.RecipientId}");
                Debug.Assert(resendRequest.RecipientId == null);

                byte[] networkPayload = new RequestCommand(CommandId.UploadResendRequest, resendRequest).Serialize(CommandHeader.Yes);
                var    tlsResponse    = await this.networkClient.SendRequestAsync(networkPayload, Transport.TCP);

                AssertOneItem(tlsResponse);

                var ret = tlsResponse[0].CommandData.DeserializeStringCore();
                if (ret == resendRequest.Id)
                {
                    response.Result = new NetworkPayloadAdded
                    {
                        NetworkResponse    = ret,
                        NetworkPayloadHash = null // TODO: check if this property is still used anywhere
                    };
                    response.SetSuccess();
                }
                else
                {
                    response.SetError("The uploaded NetworkPayloadHash and the returned value are not equal.");
                }
            }
            catch (Exception e)
            {
                this.logger.LogError(e.Message);
                response.SetError(e);
            }
            return(response);
        }
        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);
                            }
                        }
                    }
                }
            }
        }
Exemple #8
0
        public async Task <byte[]> ExecuteAuthenticatedRequestAsync(Command command)
        {
            switch (command.CommandId)
            {
            case CommandId.AnyNews:
            {
                string recipientId = command.CommandData.DeserializeStringCore();
                var    result      = await this.messageNodeRepository.AnyNews(recipientId);

                return(new RequestCommand(CommandId.AnyNews_Response, result).Serialize(CommandHeader.Yes));                                 // only when the client receives it via UDP a header is needed.
            }

            case CommandId.CheckForResendRequest:
            {
                XResendRequest resendRequestQuery = command.CommandData.DeserializeResendRequest();
                byte           result             = await this.messageNodeRepository.CheckForResendRequest(resendRequestQuery);

                return(new RequestCommand(CommandId.CheckForResendRequest_Response, result).Serialize(CommandHeader.Yes));
            }

            case CommandId.DownloadMessages:
            {
                string recipientId = command.CommandData.DeserializeStringCore();
                var    messages    = await this.messageNodeRepository.GetMessages(recipientId);

                if (messages.Count == 0)
                {
                    throw new CommandProtocolException("No message to download, please check with CommandId.AnyNews first.");                                     // connected clients get this error information
                }
                return(new RequestCommand(CommandId.DownloadMessage_Response, messages).Serialize(CommandHeader.Yes));
            }

            case CommandId.UploadMessage:
            {
                XMessage message = command.CommandData.DeserializeMessage();                                 // Deserialize only what's needed, blob-store the rest
                string   ack     = await this.messageNodeRepository.AddMessage(message);

                return(new RequestCommand(CommandId.UploadMessage_Response, ack).Serialize(CommandHeader.Yes));
            }

            case CommandId.UploadResendRequest:
            {
                XResendRequest resendRequest    = command.CommandData.DeserializeResendRequest();
                var            resendRequestAck = await this.messageNodeRepository.AddResendRequest(resendRequest);

                return(new RequestCommand(CommandId.UploadResendRequest_Response, resendRequestAck).Serialize(CommandHeader.Yes));
            }

            case CommandId.GetIdentity:
            {
                var       addedContactId = command.CommandData.DeserializeStringCore();
                XIdentity identity       = await this.messageNodeRepository.GetIdentityAsync(addedContactId);

                return(new RequestCommand(CommandId.GetIdentity_Response, identity).Serialize(CommandHeader.Yes));
            }

            case CommandId.PublishIdentity:
            {
                XIdentity identityBeingPublished = command.CommandData.DeserializeXIdentityCore();
                string    result = await this.messageNodeRepository.AddIdentity(identityBeingPublished, null);

                return(new RequestCommand(CommandId.PublishIdentity_Response, result).Serialize(CommandHeader.Yes));
            }

            default:
                throw new CommandProtocolException($"Unknown CommandId {command.CommandId}.");
            }
        }