예제 #1
0
        /// <summary>
        /// Handles situation when a client connected to a relay disconnected.
        /// However, the closed connection might be either connection to clCustomer/clNonCustomer port,
        /// or it might be connection to clAppService port.
        /// </summary>
        /// <param name="Client">Client that disconnected.</param>
        /// <param name="IsRelayConnection">true if the closed connection was to clAppService port, false otherwise.</param>
        public async Task HandleDisconnectedClient(IncomingClient Client, bool IsRelayConnection)
        {
            log.Trace("(Client.Id:{0},IsRelayConnection:{1})", Client.Id.ToHex(), IsRelayConnection);

            IncomingClient           clientToSendMessages = null;
            List <PsProtocolMessage> messagesToSend       = new List <PsProtocolMessage>();
            IncomingClient           clientToClose        = null;

            await lockObject.WaitAsync();

            bool isCallee = Client == callee;

            if (IsRelayConnection)
            {
                log.Trace("Client ({0}) ID {1} disconnected, relay '{2}' status {3}.", isCallee ? "callee" : "caller", Client.Id.ToHex(), id, status);
            }
            else
            {
                log.Trace("Client (customer) ID {0} disconnected, relay '{1}' status {2}.", Client.Id.ToHex(), id, status);
            }

            bool destroyRelay = false;

            switch (status)
            {
            case RelayConnectionStatus.WaitingForCalleeResponse:
            {
                if (isCallee)
                {
                    // The client is callee in a relay that is being initialized. The caller is waiting for callee's response and the callee has just disconnected
                    // from the profile server. This is situation 1) from the comment in ProcessMessageCallIdentityApplicationServiceRequestAsync.
                    // We have to send ERROR_NOT_AVAILABLE to the caller and destroy the relay.
                    log.Trace("Callee disconnected from clCustomer port of relay '{0}', message will be sent to the caller and relay destroyed.", id);
                    clientToSendMessages = caller;
                    messagesToSend.Add(caller.MessageBuilder.CreateErrorNotAvailableResponse(pendingMessage));
                    destroyRelay = true;
                }
                else
                {
                    // The client is caller in a relay that is being initialized. The caller was waiting for callee's response, but the caller disconnected before
                    // the callee replied. The callee is now expected to reply and either accept or reject the call. If the call is rejected, everything is OK,
                    // and we do not need to take any action. If the call is accepted, the callee will establish a new connection to clAppService port and will
                    // send us initial ApplicationServiceSendMessageRequest message. We will now destroy the relay so that the callee is disconnected
                    // as its token used in the initial message will not be found.
                    log.Trace("Caller disconnected from clCustomer port or clNonCustomer port of relay '{0}', relay will be destroyed.", id);
                    destroyRelay = true;
                }
                break;
            }

            case RelayConnectionStatus.WaitingForFirstInitMessage:
            {
                // In this relay status we do not care about connection to other than clAppService port.
                if (IsRelayConnection)
                {
                    // This should never happen because client's Relay is initialized only after
                    // its initialization message is received and that would upgrade the relay to WaitingForSecondInitMessage.
                }

                break;
            }

            case RelayConnectionStatus.WaitingForSecondInitMessage:
            {
                // In this relay status we do not care about connection to other than clAppService port.
                if (IsRelayConnection)
                {
                    // One of the clients has sent its initialization message to clAppService port
                    // and is waiting for the other client to do the same.
                    bool isWaitingClient = (callee == Client) || (caller == Client);

                    if (isWaitingClient)
                    {
                        // The client that disconnected was the waiting client. We destroy the relay.
                        // The other client is not connected yet or did not sent its initialization message yet.
                        log.Trace("First client on clAppService port of relay '{0}' closed its connection, destroying the relay.", id);
                        destroyRelay = true;
                    }
                    else
                    {
                        // The client that disconnected was the client that the first client is waiting for.
                        // We do not need to destroy the relay as the client may still connect again
                        // and send its initialization message on time.
                        log.Trace("Second client (that did not sent init message yet) on clAppService port of relay '{0}' closed its connection, no action taken.", id);
                    }
                }

                break;
            }

            case RelayConnectionStatus.Open:
            {
                // In this relay status we do not care about connection to other than clAppService port.
                if (IsRelayConnection)
                {
                    // Both clients were connected. We disconnect the other client and destroy the relay.
                    // However, there might be some unfinished ApplicationServiceSendMessageRequest requests
                    // that we have to send responses to.

                    IncomingClient otherClient = isCallee ? caller : callee;
                    log.Trace("{0} disconnected from relay '{1}', closing connection of {2}.", isCallee ? "Callee" : "Caller", id, isCallee ? "caller" : "callee");
                    clientToSendMessages = otherClient;
                    clientToClose        = otherClient;

                    // Find all unfinished requests from this relay.
                    // When a client sends ApplicationServiceSendMessageRequest, the profile server creates ApplicationServiceReceiveMessageNotificationRequest
                    // and adds it as an unfinished request with context set to RelayMessageContext, which contains the sender's ApplicationServiceSendMessageRequest.
                    // This unfinished message is in the list of unfinished message of the recipient.
                    List <UnfinishedRequest> unfinishedRelayRequests = Client.GetAndRemoveUnfinishedRequests();
                    foreach (UnfinishedRequest unfinishedRequest in unfinishedRelayRequests)
                    {
                        Message unfinishedRequestMessage = (Message)unfinishedRequest.RequestMessage.Message;
                        // Find ApplicationServiceReceiveMessageNotificationRequest request messages sent to the client who closed the connection.
                        if ((unfinishedRequestMessage.MessageTypeCase == Message.MessageTypeOneofCase.Request) &&
                            (unfinishedRequestMessage.Request.ConversationTypeCase == Request.ConversationTypeOneofCase.SingleRequest) &&
                            (unfinishedRequestMessage.Request.SingleRequest.RequestTypeCase == SingleRequest.RequestTypeOneofCase.ApplicationServiceReceiveMessageNotification))
                        {
                            // This unfinished request's context holds ApplicationServiceSendMessageRequest message of the client that is still connected.
                            RelayMessageContext ctx           = (RelayMessageContext)unfinishedRequest.Context;
                            PsProtocolMessage   responseError = clientToSendMessages.MessageBuilder.CreateErrorNotFoundResponse(ctx.SenderRequest);
                            messagesToSend.Add(responseError);
                        }
                    }

                    destroyRelay = true;
                }

                break;
            }

            case RelayConnectionStatus.Destroyed:
                // Nothing to be done.
                break;
            }

            lockObject.Release();


            if (messagesToSend.Count > 0)
            {
                foreach (PsProtocolMessage messageToSend in messagesToSend)
                {
                    if (!await clientToSendMessages.SendMessageAsync(messageToSend))
                    {
                        log.Warn("Unable to send message to the client ID {0}, relay '{1}', maybe it is not connected anymore.", clientToSendMessages.Id.ToHex(), id);
                        break;
                    }
                }
            }


            if (clientToClose != null)
            {
                await clientToClose.CloseConnectionAsync();
            }


            if (destroyRelay)
            {
                Server serverComponent = (Server)Base.ComponentDictionary[Server.ComponentName];
                await serverComponent.RelayList.DestroyNetworkRelay(this);
            }

            log.Trace("(-)");
        }
예제 #2
0
        /// <summary>
        /// Processes ApplicationServiceSendMessageRequest message from a client.
        /// <para>
        /// Relay received message from one client and sends it to the other one. If this is the first request
        /// a client sends after it connects to clAppService port, the request's message is ignored and the reply is sent
        /// to the client as the other client is confirmed to join the relay.</para>
        /// </summary>
        /// <param name="Client">Client that sent the message.</param>
        /// <param name="RequestMessage">Full request message.</param>
        /// <param name="Token">Sender's relay token.</param>
        /// <returns>Response message to be sent to the client.</returns>
        public async Task <PsProtocolMessage> ProcessIncomingMessage(IncomingClient Client, PsProtocolMessage RequestMessage, Guid Token)
        {
            log.Trace("()");

            PsProtocolMessage res = null;
            bool destroyRelay     = false;

            await lockObject.WaitAsync();

            bool isCaller = callerToken.Equals(Token);

            IncomingClient otherClient = isCaller ? callee : caller;

            log.Trace("Received message over relay '{0}' in status {1} with client ID {2} being {3} and the other client ID {4} is {5}.",
                      id, status, Client.Id.ToHex(), isCaller ? "caller" : "callee", otherClient != null ? otherClient.Id.ToHex() : "N/A", isCaller ? "callee" : "caller");

            switch (status)
            {
            case RelayConnectionStatus.WaitingForCalleeResponse:
            {
                if (!isCaller)
                {
                    // We have received a message from callee, but we did not receive its IncomingCallNotificationResponse.
                    // This may be OK if this message has been sent by callee and it just not has been processed before
                    // the callee connected to clAppService port and sent us the initialization message.
                    // In this case we will try to wait a couple of seconds and see if we receive IncomingCallNotificationResponse.
                    // If yes, we continue as if we processed the message in the right order.
                    // In all other cases, this is a fatal error and we have to destroy the relay.
                    lockObject.Release();

                    bool statusChanged = false;
                    log.Warn("Callee sent initialization message before we received IncomingCallNotificationResponse. We will wait to see if it arrives soon.");
                    for (int i = 0; i < 5; i++)
                    {
                        log.Warn("Attempt #{0}, waiting 1 second.", i + 1);
                        await Task.Delay(1000);

                        await lockObject.WaitAsync();

                        log.Warn("Attempt #{0}, checking relay status.", i + 1);
                        if (status != RelayConnectionStatus.WaitingForCalleeResponse)
                        {
                            log.Warn("Attempt #{0}, relay status changed to {1}.", i + 1, status);
                            statusChanged = true;
                        }

                        lockObject.Release();

                        if (statusChanged)
                        {
                            break;
                        }
                    }

                    await lockObject.WaitAsync();

                    if (statusChanged)
                    {
                        // Status of relay has change, which means either it has been destroyed already, or the IncomingCallNotificationResponse
                        // message we were waiting for arrived. In any case, we call this method recursively, but it can not happen that we would end up here again.
                        lockObject.Release();

                        log.Trace("Calling ProcessIncomingMessage recursively.");
                        res = await ProcessIncomingMessage(Client, RequestMessage, Token);

                        await lockObject.WaitAsync();
                    }
                    else
                    {
                        log.Trace("Message received from caller and relay status is WaitingForCalleeResponse and IncomingCallNotificationResponse did not arrive, closing connection to client, destroying relay.");
                        res = Client.MessageBuilder.CreateErrorNotFoundResponse(RequestMessage);
                        Client.ForceDisconnect = true;
                        destroyRelay           = true;
                    }
                }
                else
                {
                    log.Trace("Message received from caller and relay status is WaitingForCalleeResponse, closing connection to client, destroying relay.");
                    res = Client.MessageBuilder.CreateErrorNotFoundResponse(RequestMessage);
                    Client.ForceDisconnect = true;
                    destroyRelay           = true;
                }
                break;
            }

            case RelayConnectionStatus.WaitingForFirstInitMessage:
            {
                log.Debug("Received an initialization message from the first client ID '{0}' on relay '{1}', waiting for the second client.", Client.Id.ToHex(), id);
                CancelTimeoutTimerLocked();

                if (Client.Relay == null)
                {
                    Client.Relay = this;

                    // Other peer is not connected yet, so we put this request on hold and wait for the other client.
                    if (isCaller)
                    {
                        caller = Client;
                    }
                    else
                    {
                        callee = Client;
                    }

                    status = RelayConnectionStatus.WaitingForSecondInitMessage;
                    log.Trace("Relay '{0}' status changed to {1}.", id, status);

                    pendingMessage = RequestMessage;
                    timeoutTimer   = new Timer(TimeoutCallback, status, SecondAppServiceInitializationMessageDelayMaxSeconds * 1000, Timeout.Infinite);

                    // res remains null, which is OK as the request is put on hold until the other client joins the channel.
                }
                else
                {
                    // Client already sent us the initialization message, this is protocol violation error, destroy the relay.
                    // Since the relay should be upgraded to WaitingForSecondInitMessage status, this can happen
                    // only if a client does not use a separate connection for each clAppService session, which is forbidden.
                    log.Debug("Client ID {0} on relay '{1}' probably uses a single connection for two relays. Both relays will be destroyed.", Client.Id.ToHex(), id);
                    res          = Client.MessageBuilder.CreateErrorNotFoundResponse(RequestMessage);
                    destroyRelay = true;
                }
                break;
            }

            case RelayConnectionStatus.WaitingForSecondInitMessage:
            {
                log.Debug("Received an initialization message from the second client on relay '{0}'.", id);
                CancelTimeoutTimerLocked();

                if (Client.Relay == null)
                {
                    Client.Relay = this;

                    // Other peer is connected already, so we just inform it by sending response to its initial ApplicationServiceSendMessageRequest.
                    if (isCaller)
                    {
                        caller = Client;
                    }
                    else
                    {
                        callee = Client;
                    }

                    status = RelayConnectionStatus.Open;
                    log.Trace("Relay '{0}' status changed to {1}.", id, status);

                    PsProtocolMessage otherClientResponse = otherClient.MessageBuilder.CreateApplicationServiceSendMessageResponse(pendingMessage);
                    pendingMessage = null;
                    if (await otherClient.SendMessageAsync(otherClientResponse))
                    {
                        // And we also send reply to the second client that the channel is now ready for communication.
                        res = Client.MessageBuilder.CreateApplicationServiceSendMessageResponse(RequestMessage);
                    }
                    else
                    {
                        log.Warn("Unable to send message to other client ID {0}, closing connection to client and destroying the relay.", otherClient.Id.ToHex());
                        res = Client.MessageBuilder.CreateErrorNotFoundResponse(RequestMessage);
                        Client.ForceDisconnect = true;
                        destroyRelay           = true;
                    }
                }
                else
                {
                    // Client already sent us the initialization message, this is error, destroy the relay.
                    log.Debug("Client ID {0} on relay '{1}' sent a message before receiving a reply to its initialization message. Relay will be destroyed.", Client.Id.ToHex(), id);
                    res          = Client.MessageBuilder.CreateErrorNotFoundResponse(RequestMessage);
                    destroyRelay = true;
                }

                break;
            }


            case RelayConnectionStatus.Open:
            {
                if (Client.Relay == this)
                {
                    // Relay is open, this means that all incoming messages are sent to the other client.
                    byte[]              messageForOtherClient = RequestMessage.Request.SingleRequest.ApplicationServiceSendMessage.Message.ToByteArray();
                    PsProtocolMessage   otherClientMessage    = otherClient.MessageBuilder.CreateApplicationServiceReceiveMessageNotificationRequest(messageForOtherClient);
                    RelayMessageContext context = new RelayMessageContext(this, RequestMessage);
                    if (await otherClient.SendMessageAndSaveUnfinishedRequestAsync(otherClientMessage, context))
                    {
                        // res is null, which is fine, the sender is put on hold and we will get back to it once the recipient confirms that it received the message.
                        log.Debug("Message from client ID {0} has been relayed to other client ID {1}.", Client.Id.ToHex(), otherClient.Id.ToHex());
                    }
                    else
                    {
                        log.Warn("Unable to relay message to other client ID {0}, closing connection to client and destroying the relay.", otherClient.Id.ToHex());
                        res = Client.MessageBuilder.CreateErrorNotFoundResponse(RequestMessage);
                        Client.ForceDisconnect = true;
                        destroyRelay           = true;
                    }
                }
                else
                {
                    // This means that the client used a single clAppService port connection for two different relays, which is forbidden.
                    log.Warn("Client ID {0} mixed relay '{1}' with relay '{2}', closing connection to client and destroying both relays.", otherClient.Id.ToHex(), Client.Relay.id, id);
                    res = Client.MessageBuilder.CreateErrorNotFoundResponse(RequestMessage);
                    Client.ForceDisconnect = true;
                    destroyRelay           = true;
                }

                break;
            }

            case RelayConnectionStatus.Destroyed:
            {
                log.Trace("Relay has been destroyed, closing connection to client.");
                res = Client.MessageBuilder.CreateErrorNotFoundResponse(RequestMessage);
                Client.ForceDisconnect = true;
                break;
            }

            default:
                log.Trace("Relay status is '{0}', closing connection to client, destroying relay.", status);
                res = Client.MessageBuilder.CreateErrorNotFoundResponse(RequestMessage);
                Client.ForceDisconnect = true;
                destroyRelay           = true;
                break;
            }

            lockObject.Release();

            if (destroyRelay)
            {
                Server serverComponent = (Server)Base.ComponentDictionary[Server.ComponentName];
                await serverComponent.RelayList.DestroyNetworkRelay(this);

                if ((this != Client.Relay) && (Client.Relay != null))
                {
                    await serverComponent.RelayList.DestroyNetworkRelay(Client.Relay);
                }
            }

            log.Trace("(-)");
            return(res);
        }