Exemplo n.º 1
0
        /// <summary>
        ///     Returns an existing, or gets a new connection using the details in the specified
        ///     <paramref name="connectToPeerResponse"/> and pierces the remote peer's firewall.
        /// </summary>
        /// <remarks>
        ///     This method will be invoked from <see cref="Messaging.Handlers.ServerMessageHandler"/> upon receipt of an
        ///     unsolicited <see cref="ConnectToPeerResponse"/> of type 'P' only. This connection should only be initiated if
        ///     there is no existing connection; superceding should be avoided if possible.
        /// </remarks>
        /// <param name="connectToPeerResponse">The response that solicited the connection.</param>
        /// <returns>The operation context, including the new or updated connection.</returns>
        public async Task <IMessageConnection> GetOrAddMessageConnectionAsync(ConnectToPeerResponse connectToPeerResponse)
        {
            bool cached = true;
            var  r      = connectToPeerResponse;

            try
            {
                var connection = await MessageConnectionDictionary.GetOrAdd(
                    r.Username,
                    key => new Lazy <Task <IMessageConnection> >(() => GetConnection())).Value.ConfigureAwait(false);

                if (cached)
                {
                    Diagnostic.Debug($"Retrieved cached message connection to {r.Username} ({r.IPEndPoint})");
                }

                return(connection);
            }
            catch (Exception ex)
            {
                var msg = $"Failed to establish an inbound indirect message connection to {r.Username} ({r.IPEndPoint}): {ex.Message}";
                Diagnostic.Debug(msg);
                Diagnostic.Debug($"Purging message connection cache of failed connection to {r.Username} ({r.IPEndPoint}).");
                MessageConnectionDictionary.TryRemove(r.Username, out _);
                throw new ConnectionException(msg, ex);
            }

            async Task <IMessageConnection> GetConnection()
            {
                cached = false;

                Diagnostic.Debug($"Attempting inbound indirect message connection to {r.Username} ({r.IPEndPoint}) for token {r.Token}");

                var connection = ConnectionFactory.GetMessageConnection(
                    r.Username,
                    r.IPEndPoint,
                    SoulseekClient.Options.PeerConnectionOptions);

                connection.Type             = ConnectionTypes.Inbound | ConnectionTypes.Indirect;
                connection.MessageRead     += SoulseekClient.PeerMessageHandler.HandleMessageRead;
                connection.MessageReceived += SoulseekClient.PeerMessageHandler.HandleMessageReceived;
                connection.Disconnected    += MessageConnection_Disconnected;

                try
                {
                    await connection.ConnectAsync().ConfigureAwait(false);

                    var request = new PierceFirewall(r.Token).ToByteArray();
                    await connection.WriteAsync(request).ConfigureAwait(false);
                }
                catch
                {
                    connection.Dispose();
                    throw;
                }

                Diagnostic.Debug($"Message connection to {r.Username} ({r.IPEndPoint}) established. (type: {connection.Type}, id: {connection.Id})");
                return(connection);
            }
        }
Exemplo n.º 2
0
        /// <summary>
        ///     Gets a new or existing message connection to the specified <paramref name="username"/>.
        /// </summary>
        /// <remarks>
        ///     If a connection doesn't exist, new direct and indirect connections are attempted simultaneously, and the first to
        ///     connect is returned.
        /// </remarks>
        /// <param name="username">The username of the user to which to connect.</param>
        /// <param name="ipEndPoint">The remote IP endpoint of the connection.</param>
        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
        /// <returns>The operation context, including the new or existing connection.</returns>
        public async Task <IMessageConnection> GetOrAddMessageConnectionAsync(string username, IPEndPoint ipEndPoint, CancellationToken cancellationToken)
        {
            bool cached = true;

            try
            {
                var connection = await MessageConnectionDictionary.GetOrAdd(
                    username,
                    key => new Lazy <Task <IMessageConnection> >(() => GetConnection())).Value.ConfigureAwait(false);

                if (cached)
                {
                    Diagnostic.Debug($"Retrieved cached message connection to {username} ({ipEndPoint}) (type: {connection.Type}, id: {connection.Id})");
                }

                return(connection);
            }
            catch
            {
                Diagnostic.Debug($"Purging message connection cache of failed connection to {username} ({ipEndPoint}).");
                MessageConnectionDictionary.TryRemove(username, out _);
                throw;
            }

            async Task <IMessageConnection> GetConnection()
            {
                cached = false;

                using var directCts         = new CancellationTokenSource();
                using var directLinkedCts   = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, directCts.Token);
                using var indirectCts       = new CancellationTokenSource();
                using var indirectLinkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, indirectCts.Token);

                Diagnostic.Debug($"Attempting simultaneous direct and indirect message connections to {username} ({ipEndPoint})");

                var direct   = GetMessageConnectionOutboundDirectAsync(username, ipEndPoint, directLinkedCts.Token);
                var indirect = GetMessageConnectionOutboundIndirectAsync(username, indirectLinkedCts.Token);

                var tasks = new[] { direct, indirect }.ToList();
                Task <IMessageConnection> task;

                do
                {
                    task = await Task.WhenAny(tasks).ConfigureAwait(false);

                    tasks.Remove(task);
                }while (task.Status != TaskStatus.RanToCompletion && tasks.Count > 0);

                if (task.Status != TaskStatus.RanToCompletion)
                {
                    var msg = $"Failed to establish a direct or indirect message connection to {username} ({ipEndPoint})";
                    Diagnostic.Debug(msg);
                    throw new ConnectionException(msg);
                }

                var connection = await task.ConfigureAwait(false);

                connection.Disconnected += MessageConnection_Disconnected;

                var isDirect = task == direct;

                Diagnostic.Debug($"{(isDirect ? "Direct" : "Indirect")} message connection to {username} ({ipEndPoint}) established first, attempting to cancel {(isDirect ? "indirect" : "direct")} connection.");
                (isDirect ? indirectCts : directCts).Cancel();

                try
                {
                    if (isDirect)
                    {
                        var request = new PeerInit(SoulseekClient.Username, Constants.ConnectionType.Peer, SoulseekClient.GetNextToken());
                        await connection.WriteAsync(request, cancellationToken).ConfigureAwait(false);
                    }
                    else
                    {
                        connection.StartReadingContinuously();
                    }
                }
                catch (Exception ex)
                {
                    var msg = $"Failed to negotiate message connection to {username} ({ipEndPoint}): {ex.Message}";
                    Diagnostic.Debug($"{msg} (type: {connection.Type}, id: {connection.Id})");
                    connection.Dispose();
                    throw new ConnectionException(msg, ex);
                }

                Diagnostic.Debug($"Message connection to {username} ({ipEndPoint}) established. (type: {connection.Type}, id: {connection.Id})");
                return(connection);
            }
        }
Exemplo n.º 3
0
        /// <summary>
        ///     Returns an existing, or gets a new connection using the details in the specified
        ///     <paramref name="connectToPeerResponse"/> and pierces the remote peer's firewall.
        /// </summary>
        /// <remarks>
        ///     This method will be invoked from <see cref="Messaging.Handlers.ServerMessageHandler"/> upon receipt of an
        ///     unsolicited <see cref="ConnectToPeerResponse"/> of type 'P' only. This connection should only be initiated if
        ///     there is no existing connection; superseding should be avoided if possible.
        /// </remarks>
        /// <param name="connectToPeerResponse">The response that solicited the connection.</param>
        /// <returns>The operation context, including the new or updated connection.</returns>
        public async Task <IMessageConnection> GetOrAddMessageConnectionAsync(ConnectToPeerResponse connectToPeerResponse)
        {
            bool cached = true;
            var  r      = connectToPeerResponse;

            try
            {
                var connection = await MessageConnectionDictionary.GetOrAdd(
                    r.Username,
                    key => new Lazy <Task <IMessageConnection> >(() => GetConnection())).Value.ConfigureAwait(false);

                if (cached)
                {
                    Diagnostic.Debug($"Retrieved cached message connection to {r.Username} ({r.IPEndPoint}) (type: {connection.Type}, id: {connection.Id})");
                }

                return(connection);
            }
            catch (Exception ex)
            {
                var msg = $"Failed to establish an inbound indirect message connection to {r.Username} ({r.IPEndPoint}): {ex.Message}";
                Diagnostic.Debug(msg);
                Diagnostic.Debug($"Purging message connection cache of failed connection to {r.Username} ({r.IPEndPoint}).");
                MessageConnectionDictionary.TryRemove(r.Username, out _);

                throw new ConnectionException(msg, ex);
            }

            async Task <IMessageConnection> GetConnection()
            {
                cached = false;

                Diagnostic.Debug($"Attempting inbound indirect message connection to {r.Username} ({r.IPEndPoint}) for token {r.Token}");

                var connection = ConnectionFactory.GetMessageConnection(
                    r.Username,
                    r.IPEndPoint,
                    SoulseekClient.Options.PeerConnectionOptions);

                connection.Type             = ConnectionTypes.Inbound | ConnectionTypes.Indirect;
                connection.MessageRead     += SoulseekClient.PeerMessageHandler.HandleMessageRead;
                connection.MessageReceived += SoulseekClient.PeerMessageHandler.HandleMessageReceived;
                connection.MessageWritten  += SoulseekClient.PeerMessageHandler.HandleMessageWritten;
                connection.Disconnected    += MessageConnection_Disconnected;

                using (var cts = new CancellationTokenSource())
                {
                    // add a record to the pending dictionary so we can tell whether the following code is waiting
                    PendingInboundIndirectConnectionDictionary.AddOrUpdate(r.Username, cts, (username, existingCts) => cts);

                    try
                    {
                        await connection.ConnectAsync(cts.Token).ConfigureAwait(false);

                        var request = new PierceFirewall(r.Token);
                        await connection.WriteAsync(request, cts.Token).ConfigureAwait(false);
                    }
                    catch
                    {
                        connection.Dispose();
                        throw;
                    }
                    finally
                    {
                        // let everyone know this code is done executing and that .Value of the containing cache is safe to await with no delay.
                        PendingInboundIndirectConnectionDictionary.TryRemove(r.Username, out _);
                    }
                }

                Diagnostic.Debug($"Message connection to {r.Username} ({r.IPEndPoint}) established. (type: {connection.Type}, id: {connection.Id})");
                return(connection);
            }
        }
        /// <summary>
        ///     Returns an existing, or gets a new connection using the details in the specified
        ///     <paramref name="connectToPeerResponse"/> and pierces the remote peer's firewall.
        /// </summary>
        /// <remarks>
        ///     This method will be invoked from <see cref="Messaging.Handlers.ServerMessageHandler"/> upon receipt of an
        ///     unsolicited <see cref="ConnectToPeerResponse"/> of type 'P' only. This connection should only be initiated if
        ///     there is no existing connection; superseding should be avoided if possible.
        /// </remarks>
        /// <param name="connectToPeerResponse">The response that solicited the connection.</param>
        /// <returns>The operation context, including the new or updated connection.</returns>
        public async Task <IMessageConnection> GetOrAddMessageConnectionAsync(ConnectToPeerResponse connectToPeerResponse)
        {
            bool cached = true;
            var  r      = connectToPeerResponse;

            try
            {
                var connection = await MessageConnectionDictionary.GetOrAdd(
                    r.Username,
                    key => new Lazy <Task <IMessageConnection> >(() => GetConnection())).Value.ConfigureAwait(false);

                if (cached)
                {
                    Diagnostic.Debug($"Retrieved cached message connection to {r.Username} ({r.IPEndPoint}) (type: {connection.Type}, id: {connection.Id})");
                }

                return(connection);
            }
            catch (Exception ex)
            {
                var msg = $"Failed to establish an inbound indirect message connection to {r.Username} ({r.IPEndPoint}): {ex.Message}";
                Diagnostic.Debug(msg);

                // only purge the connection if the thrown exception is something other than OperationCanceledException. if this
                // is thrown then a direct connection superseded this connection while it was being established, and
                // ChildConnectionDictionary contains the new, direct connection.
                if (!(ex is OperationCanceledException))
                {
                    Diagnostic.Debug($"Purging message connection cache of failed connection to {r.Username} ({r.IPEndPoint}).");

                    // remove the current record, which *should* be the one we added above.
                    MessageConnectionDictionary.TryRemove(r.Username, out var removed);

                    try
                    {
                        var connection = await removed.Value.ConfigureAwait(false);

                        // if the connection we removed is Direct, then a direct connection managed to come in after this attempt
                        // had timed out or failed, but before that connection was able to cancel the pending token this should be
                        // an extreme edge case, but log it as a warning so we can see how common it is.
                        if (connection.Type.HasFlag(ConnectionTypes.Direct))
                        {
                            Diagnostic.Warning($"Erroneously purged direct message connection to {r.Username} upon indirect failure");
                            MessageConnectionDictionary.TryAdd(r.Username, removed);
                        }
                    }
                    catch
                    {
                        // noop
                    }
                }

                throw new ConnectionException(msg, ex);
            }

            async Task <IMessageConnection> GetConnection()
            {
                cached = false;

                Diagnostic.Debug($"Attempting inbound indirect message connection to {r.Username} ({r.IPEndPoint}) for token {r.Token}");

                var connection = ConnectionFactory.GetMessageConnection(
                    r.Username,
                    r.IPEndPoint,
                    SoulseekClient.Options.PeerConnectionOptions);

                connection.Type             = ConnectionTypes.Inbound | ConnectionTypes.Indirect;
                connection.MessageRead     += SoulseekClient.PeerMessageHandler.HandleMessageRead;
                connection.MessageReceived += SoulseekClient.PeerMessageHandler.HandleMessageReceived;
                connection.MessageWritten  += SoulseekClient.PeerMessageHandler.HandleMessageWritten;

                using (var cts = new CancellationTokenSource())
                {
                    // add a record to the pending dictionary so we can tell whether the following code is waiting
                    PendingInboundIndirectConnectionDictionary.AddOrUpdate(r.Username, cts, (username, existingCts) => cts);

                    try
                    {
                        await connection.ConnectAsync(cts.Token).ConfigureAwait(false);

                        var request = new PierceFirewall(r.Token);
                        await connection.WriteAsync(request.ToByteArray(), cts.Token).ConfigureAwait(false);
                    }
                    catch
                    {
                        connection.Dispose();
                        throw;
                    }
                    finally
                    {
                        // let everyone know this code is done executing and that .Value of the containing cache is safe to await
                        // with no delay.
                        PendingInboundIndirectConnectionDictionary.TryRemove(r.Username, out _);
                    }
                }

                connection.Disconnected += MessageConnection_Disconnected;

                Diagnostic.Debug($"Message connection to {r.Username} ({r.IPEndPoint}) established. (type: {connection.Type}, id: {connection.Id})");
                return(connection);
            }
        }