示例#1
0
        /// <summary>
        ///     Creates a new instance of <see cref="PeerInit"/> from the specified <paramref name="bytes"/>.
        /// </summary>
        /// <param name="bytes">The byte array from which to parse.</param>
        /// <param name="response">The parsed instance.</param>
        /// <returns>A value indicating whether the message was successfully parsed.</returns>
        public static bool TryFromByteArray(byte[] bytes, out PeerInit response)
        {
            response = null;

            try
            {
                var reader = new MessageReader <MessageCode.Initialization>(bytes);

                if (reader.ReadCode() != MessageCode.Initialization.PeerInit)
                {
                    return(false);
                }

                var username     = reader.ReadString();
                var transferType = reader.ReadString();
                var token        = reader.ReadInteger();

                response = new PeerInit(username, transferType, token);
                return(true);
            }
            catch
            {
                return(false);
            }
        }
示例#2
0
        public void Instantiates_With_The_Given_Data(string username, string transferType, int token)
        {
            var r = new PeerInit(username, transferType, token);

            Assert.Equal(username, r.Username);
            Assert.Equal(transferType, r.ConnectionType);
            Assert.Equal(token, r.Token);
        }
示例#3
0
        /// <summary>
        ///     Gets a new transfer connection to the specified <paramref name="username"/> using the specified <paramref name="token"/>.
        /// </summary>
        /// <remarks>A direct connection is attempted first, and, if unsuccessful, an indirect connection is attempted.</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="token">The token with which to initialize the connection.</param>
        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
        /// <returns>The operation context, including the new connection.</returns>
        public async Task <IConnection> GetTransferConnectionAsync(string username, IPEndPoint ipEndPoint, int token, CancellationToken cancellationToken)
        {
            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 transfer connections to {username} ({ipEndPoint})");

                            var direct   = GetTransferConnectionOutboundDirectAsync(ipEndPoint, token, directLinkedCts.Token);
                            var indirect = GetTransferConnectionOutboundIndirectAsync(username, token, indirectLinkedCts.Token);

                            var tasks = new[] { direct, indirect }.ToList();
                            Task <IConnection> 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 transfer connection to {username} ({ipEndPoint})";
                                Diagnostic.Debug(msg);
                                throw new ConnectionException(msg);
                            }

                            var connection = await task.ConfigureAwait(false);

                            var isDirect = task == direct;

                            Diagnostic.Debug($"{(isDirect ? "Direct" : "Indirect")} transfer 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.Transfer, token).ToByteArray();
                                    await connection.WriteAsync(request, cancellationToken).ConfigureAwait(false);
                                }

                                await connection.WriteAsync(BitConverter.GetBytes(token), cancellationToken).ConfigureAwait(false);
                            }
                            catch (Exception ex)
                            {
                                var msg = $"Failed to negotiate transfer connection to {username} ({ipEndPoint}): {ex.Message}";
                                Diagnostic.Debug($"{msg} (type: {connection.Type}, id: {connection.Id})");
                                connection.Dispose();
                                throw new ConnectionException(msg, ex);
                            }

                            Diagnostic.Debug($"Transfer connection to {username} ({ipEndPoint}) established. (type: {connection.Type}, id: {connection.Id})");
                            return(connection);
                        }
        }
示例#4
0
        public void TryParse_Returns_False_On_Code_Mismatch()
        {
            var msg = new List <byte>();

            msg.AddRange(BitConverter.GetBytes(0)); // overall length, ignored for this test.
            msg.Add((byte)MessageCode.Initialization.PierceFirewall);

            var r = PeerInit.TryFromByteArray(msg.ToArray(), out var result);

            Assert.False(r);
            Assert.Null(result);
        }
示例#5
0
        public void Adds_Distributed_Connection_On_Distributed_PeerInit(IPEndPoint endpoint, string username, int token)
        {
            var(handler, mocks) = GetFixture(endpoint);

            var message      = new PeerInit(username, Constants.ConnectionType.Distributed, token);
            var messageBytes = message.ToByteArray().AsSpan().Slice(4).ToArray();

            mocks.Connection.Setup(m => m.ReadAsync(4, It.IsAny <CancellationToken?>()))
            .Returns(Task.FromResult(BitConverter.GetBytes(messageBytes.Length)));

            mocks.Connection.Setup(m => m.ReadAsync(messageBytes.Length, It.IsAny <CancellationToken?>()))
            .Returns(Task.FromResult(messageBytes));

            handler.HandleConnection(null, mocks.Connection.Object);

            mocks.DistributedConnectionManager.Verify(m => m.AddChildConnectionAsync(username, It.IsAny <IConnection>()));
        }
示例#6
0
        public void Adds_Transfer_Connection_On_Transfer_PeerInit(IPAddress ip, string username, int token)
        {
            var(handler, mocks) = GetFixture(ip);

            var message      = new PeerInit(username, Constants.ConnectionType.Transfer, token);
            var messageBytes = message.ToByteArray().AsSpan().Slice(4).ToArray();

            mocks.Connection.Setup(m => m.ReadAsync(4, It.IsAny <CancellationToken?>()))
            .Returns(Task.FromResult(BitConverter.GetBytes(messageBytes.Length)));

            mocks.Connection.Setup(m => m.ReadAsync(messageBytes.Length, It.IsAny <CancellationToken?>()))
            .Returns(Task.FromResult(messageBytes));

            handler.HandleConnection(null, mocks.Connection.Object);

            mocks.PeerConnectionManager.Verify(m => m.AddTransferConnectionAsync(username, token, It.IsAny <ITcpClient>()));
        }
示例#7
0
        public void PeerInitRequest_Constructs_The_Correct_Message()
        {
            var name  = Guid.NewGuid().ToString();
            var token = new Random().Next();
            var a     = new PeerInit(name, "P", token);
            var msg   = a.ToByteArray();

            var reader = new MessageReader <MessageCode.Initialization>(msg);
            var code   = reader.ReadCode();

            Assert.Equal(MessageCode.Initialization.PeerInit, code);
            Assert.Equal(4 + 1 + 4 + name.Length + "P".Length + 8, msg.Length);

            Assert.Equal(name, reader.ReadString());
            Assert.Equal("P", reader.ReadString());
            Assert.Equal(token, reader.ReadInteger());
        }
示例#8
0
        public void TryParse_Returns_False_On_Missing_Data(string username, char type)
        {
            var msg = new List <byte>();

            msg.AddRange(BitConverter.GetBytes(0)); // overall length, ignored for this test.
            msg.Add((byte)MessageCode.Initialization.PeerInit);

            msg.AddRange(BitConverter.GetBytes(3));                                             // name len
            msg.AddRange(Encoding.ASCII.GetBytes(username));                                    // name
            msg.AddRange(BitConverter.GetBytes(1));                                             // type len
            msg.AddRange(Encoding.ASCII.GetBytes(type.ToString(CultureInfo.InvariantCulture))); // type

            // omit token
            var r = PeerInit.TryFromByteArray(msg.ToArray(), out var result);

            Assert.False(r);
            Assert.Null(result);
        }
示例#9
0
        public void Creates_Diagnostic_On_PeerInit(IPEndPoint endpoint, string username, int token)
        {
            var(handler, mocks) = GetFixture(endpoint);

            mocks.Diagnostic.Setup(m => m.Debug(It.IsAny <string>()));

            var message      = new PeerInit(username, Constants.ConnectionType.Peer, token);
            var messageBytes = message.ToByteArray().AsSpan().Slice(4).ToArray();

            mocks.Connection.Setup(m => m.ReadAsync(4, It.IsAny <CancellationToken?>()))
            .Returns(Task.FromResult(BitConverter.GetBytes(messageBytes.Length)));

            mocks.Connection.Setup(m => m.ReadAsync(messageBytes.Length, It.IsAny <CancellationToken?>()))
            .Returns(Task.FromResult(messageBytes));

            handler.HandleConnection(null, mocks.Connection.Object);

            var compare = StringComparison.InvariantCultureIgnoreCase;

            mocks.Diagnostic.Verify(m => m.Debug(It.Is <string>(s => s.Contains("PeerInit for connection type", compare))), Times.Once);
        }
示例#10
0
        public void TryParse_Returns_Expected_Data(string username, char type, int token)
        {
            var msg = new List <byte>();

            msg.AddRange(BitConverter.GetBytes(0)); // overall length, ignored for this test.
            msg.Add((byte)MessageCode.Initialization.PeerInit);

            msg.AddRange(BitConverter.GetBytes(username.Length));                               // name len
            msg.AddRange(Encoding.ASCII.GetBytes(username));                                    // name
            msg.AddRange(BitConverter.GetBytes(1));                                             // type len
            msg.AddRange(Encoding.ASCII.GetBytes(type.ToString(CultureInfo.InvariantCulture))); // type
            msg.AddRange(BitConverter.GetBytes(token));

            // omit token
            var r = PeerInit.TryFromByteArray(msg.ToArray(), out var result);

            Assert.True(r);
            Assert.NotNull(result);

            Assert.Equal(username, result.Username);
            Assert.Equal(type.ToString(CultureInfo.InvariantCulture), result.ConnectionType);
            Assert.Equal(token, result.Token);
        }
示例#11
0
        public void Completes_DirectTransfer_Wait_On_Transfer_PeerInit(IPEndPoint endpoint, string username, int token)
        {
            var(handler, mocks) = GetFixture(endpoint);

            var message      = new PeerInit(username, Constants.ConnectionType.Transfer, token);
            var messageBytes = message.ToByteArray().AsSpan().Slice(4).ToArray();

            mocks.Connection.Setup(m => m.ReadAsync(4, It.IsAny <CancellationToken?>()))
            .Returns(Task.FromResult(BitConverter.GetBytes(messageBytes.Length)));

            mocks.Connection.Setup(m => m.ReadAsync(messageBytes.Length, It.IsAny <CancellationToken?>()))
            .Returns(Task.FromResult(messageBytes));

            var newTransfer = new Mock <IConnection>();

            mocks.PeerConnectionManager.Setup(m => m.AddTransferConnectionAsync(username, token, It.IsAny <IConnection>()))
            .Returns(Task.FromResult((newTransfer.Object, token)));

            handler.HandleConnection(null, mocks.Connection.Object);

            var key = new WaitKey(Constants.WaitKey.DirectTransfer, username, token);

            mocks.Waiter.Verify(m => m.Complete(key, newTransfer.Object), Times.Once);
        }
示例#12
0
        /// <summary>
        ///     Handle <see cref="IListener.Accepted"/> events.
        /// </summary>
        /// <param name="sender">The originating <see cref="IListener"/> instance.</param>
        /// <param name="connection">The accepted connection.</param>
        public async void HandleConnection(object sender, IConnection connection)
        {
            Diagnostic.Debug($"Accepted incoming connection from {connection.IPEndPoint.Address}:{SoulseekClient.Listener.Port} (id: {connection.Id})");

            try
            {
                var lengthBytes = await connection.ReadAsync(4).ConfigureAwait(false);

                var length = BitConverter.ToInt32(lengthBytes, 0);

                var bodyBytes = await connection.ReadAsync(length).ConfigureAwait(false);

                byte[] message = lengthBytes.Concat(bodyBytes).ToArray();

                if (PeerInit.TryFromByteArray(message, out var peerInit))
                {
                    // this connection is the result of an unsolicited connection from the remote peer, either to request info or
                    // browse, or to send a file.
                    Diagnostic.Debug($"PeerInit for connection type {peerInit.ConnectionType} received from {peerInit.Username} ({connection.IPEndPoint.Address}:{SoulseekClient.Listener.Port}) (id: {connection.Id})");

                    if (peerInit.ConnectionType == Constants.ConnectionType.Peer)
                    {
                        await SoulseekClient.PeerConnectionManager.AddMessageConnectionAsync(
                            peerInit.Username,
                            connection).ConfigureAwait(false);
                    }
                    else if (peerInit.ConnectionType == Constants.ConnectionType.Transfer)
                    {
                        var(transferConnection, remoteToken) = await SoulseekClient.PeerConnectionManager.AddTransferConnectionAsync(
                            peerInit.Username,
                            peerInit.Token,
                            connection).ConfigureAwait(false);

                        SoulseekClient.Waiter.Complete(new WaitKey(Constants.WaitKey.DirectTransfer, peerInit.Username, remoteToken), transferConnection);
                    }
                    else if (peerInit.ConnectionType == Constants.ConnectionType.Distributed)
                    {
                        await SoulseekClient.DistributedConnectionManager.AddChildConnectionAsync(
                            peerInit.Username,
                            connection).ConfigureAwait(false);
                    }
                }
                else if (PierceFirewall.TryFromByteArray(message, out var pierceFirewall))
                {
                    // this connection is the result of a ConnectToPeer request sent to the user, and the incoming message will
                    // contain the token that was provided in the request. Ensure this token is among those expected, and use it
                    // to determine the username of the remote user.
                    if (SoulseekClient.PeerConnectionManager.PendingSolicitations.TryGetValue(pierceFirewall.Token, out var peerUsername))
                    {
                        Diagnostic.Debug($"Peer PierceFirewall with token {pierceFirewall.Token} received from {peerUsername} ({connection.IPEndPoint.Address}:{SoulseekClient.Listener.Port}) (id: {connection.Id})");
                        SoulseekClient.Waiter.Complete(new WaitKey(Constants.WaitKey.SolicitedPeerConnection, peerUsername, pierceFirewall.Token), connection);
                    }
                    else if (SoulseekClient.DistributedConnectionManager.PendingSolicitations.TryGetValue(pierceFirewall.Token, out var distributedUsername))
                    {
                        Diagnostic.Debug($"Distributed PierceFirewall with token {pierceFirewall.Token} received from {distributedUsername} ({connection.IPEndPoint.Address}:{SoulseekClient.Listener.Port}) (id: {connection.Id})");
                        SoulseekClient.Waiter.Complete(new WaitKey(Constants.WaitKey.SolicitedDistributedConnection, distributedUsername, pierceFirewall.Token), connection);
                    }
                    else
                    {
                        throw new ConnectionException($"Unknown PierceFirewall attempt with token {pierceFirewall.Token} from {connection.IPEndPoint.Address}:{connection.IPEndPoint.Port} (id: {connection.Id})");
                    }
                }
                else
                {
                    throw new ConnectionException($"Unrecognized initialization message: {BitConverter.ToString(message)} ({message.Length} bytes, id: {connection.Id})");
                }
            }
            catch (Exception ex)
            {
                Diagnostic.Debug($"Failed to initialize direct connection from {connection.IPEndPoint.Address}:{connection.IPEndPoint.Port}: {ex.Message}");
                connection.Disconnect(exception: ex);
                connection.Dispose();
            }
        }
示例#13
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);
            }
        }