Esempio n. 1
0
        internal async Task GetTransferConnectionAsync_Connects_And_Pierces_Firewall(string username, string type, IPAddress ipAddress, int port, int token, ConnectionOptions options)
        {
            var conn = new Mock <IConnection>();

            conn.Setup(m => m.IPAddress)
            .Returns(ipAddress);
            conn.Setup(m => m.Port)
            .Returns(port);
            conn.Setup(m => m.ConnectAsync())
            .Returns(Task.CompletedTask);
            conn.Setup(m => m.WriteAsync(It.IsAny <byte[]>()))
            .Returns(Task.CompletedTask);

            var connFactory = new Mock <IConnectionFactory>();

            connFactory.Setup(m => m.GetConnection(It.IsAny <IPAddress>(), It.IsAny <int>(), It.IsAny <ConnectionOptions>()))
            .Returns(conn.Object);

            var response = new ConnectToPeerResponse(username, type, ipAddress, port, token);

            var s = new SoulseekClient("127.0.0.1", 1, connectionFactory: connFactory.Object);

            await s.InvokeMethod <Task <IConnection> >("GetTransferConnectionAsync", response, options, CancellationToken.None);

            conn.Verify(m => m.ConnectAsync(It.IsAny <CancellationToken>()), Times.Once);
            conn.Verify(m => m.WriteAsync(It.IsAny <byte[]>(), It.IsAny <CancellationToken>()), Times.Once);
        }
        public async Task Completes_Start_Wait_When_Download_Exists(string username, string filename, int token, int remoteToken)
        {
            var conn = new Mock <IConnection>();

            conn.Setup(m => m.ReadAsync(It.IsAny <int>(), null))
            .Returns(Task.FromResult(BitConverter.GetBytes(remoteToken)));

            var connManager = new Mock <IConnectionManager>();

            connManager.Setup(m => m.AddSolicitedTransferConnectionAsync(It.IsAny <ConnectToPeerResponse>(), It.IsAny <ConnectionOptions>(), It.IsAny <CancellationToken>()))
            .Returns(Task.FromResult(conn.Object));

            var waiter = new Mock <IWaiter>();

            var s = new SoulseekClient("127.0.0.1", 1, null, waiter: waiter.Object, connectionManager: connManager.Object);

            var activeDownloads = new ConcurrentDictionary <int, Download>();
            var download        = new Download(username, filename, token)
            {
                RemoteToken = remoteToken
            };

            activeDownloads.TryAdd(token, download);

            s.SetProperty("Downloads", activeDownloads);

            var r = new ConnectToPeerResponse(username, "F", IPAddress.Parse("127.0.0.1"), 1, token);

            await s.InvokeMethod <Task>("InitializeDownloadAsync", r);

            waiter.Verify(m => m.Complete(download.WaitKey, It.IsAny <IConnection>()), Times.Once);
        }
        public async Task Does_Not_Throw_On_Remote_Token_Read_Exception(string username, string filename, int token)
        {
            var conn = new Mock <IConnection>();

            conn.Setup(m => m.ReadAsync(It.IsAny <int>(), It.IsAny <CancellationToken>()))
            .Returns(Task.FromException <byte[]>(new Exception()));

            var connManager = new Mock <IConnectionManager>();

            connManager.Setup(m => m.AddSolicitedTransferConnectionAsync(It.IsAny <ConnectToPeerResponse>(), It.IsAny <ConnectionOptions>(), It.IsAny <CancellationToken>()))
            .Returns(Task.FromResult(conn.Object));

            var waiter = new Mock <IWaiter>();

            var s = new SoulseekClient("127.0.0.1", 1, null, waiter: waiter.Object, connectionManager: connManager.Object);

            var activeDownloads = new ConcurrentDictionary <int, Download>();
            var download        = new Download(username, filename, token);

            activeDownloads.TryAdd(token, download);

            s.SetProperty("Downloads", activeDownloads);

            var r = new ConnectToPeerResponse(username, "F", IPAddress.Parse("127.0.0.1"), 1, token);

            var ex = await Record.ExceptionAsync(async() => await s.InvokeMethod <Task>("InitializeDownloadAsync", r));

            Assert.Null(ex);
            waiter.Verify(m => m.Complete(download.WaitKey), Times.Never);
        }
        public async Task Transfer_Exception_Disconnects_And_Does_Not_Throw(string username, string filename, int token)
        {
            string message = null;

            var conn = new Mock <IConnection>();

            conn.Setup(m => m.ReadAsync(It.IsAny <int>(), null))
            .Returns(Task.FromException <byte[]>(new Exception("fake exception")));
            conn.Setup(m => m.Disconnect(It.IsAny <string>()))
            .Callback <string>(str => message = str);

            var connManager = new Mock <IConnectionManager>();

            connManager.Setup(m => m.AddSolicitedTransferConnectionAsync(It.IsAny <ConnectToPeerResponse>(), It.IsAny <ConnectionOptions>(), It.IsAny <CancellationToken>()))
            .Returns(Task.FromResult(conn.Object));

            var waiter = new Mock <IWaiter>();

            var s = new SoulseekClient("127.0.0.1", 1, null, waiter: waiter.Object, connectionManager: connManager.Object);

            var activeDownloads = new ConcurrentDictionary <int, Download>();
            var download        = new Download(username, filename, token);

            activeDownloads.TryAdd(token, download);

            s.SetProperty("Downloads", activeDownloads);

            var r = new ConnectToPeerResponse(username, "F", IPAddress.Parse("127.0.0.1"), 1, token);

            var ex = await Record.ExceptionAsync(async() => await s.InvokeMethod <Task>("InitializeDownloadAsync", r));

            Assert.Null(ex);
            conn.Verify(m => m.Disconnect(It.IsAny <string>()), Times.Once);
            Assert.Contains("fake exception", message, StringComparison.InvariantCultureIgnoreCase);
        }
Esempio n. 5
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);
            }
        }
        public void Creates_Connection_On_ConnectToPeerResponse_P(string username, int token, IPAddress ip, int port)
        {
            ConnectToPeerResponse response = null;

            var connMgr = new Mock <IConnectionManager>();

            connMgr
            .Setup(m => m.GetOrAddSolicitedConnectionAsync(It.IsAny <ConnectToPeerResponse>(), It.IsAny <EventHandler <Message> >(), It.IsAny <ConnectionOptions>(), It.IsAny <CancellationToken>()))
            .Returns(Task.FromResult(new Mock <IMessageConnection>().Object))
            .Callback <ConnectToPeerResponse, EventHandler <Message>, ConnectionOptions, CancellationToken>((r, e, c, t) => response = r);

            var ipBytes = ip.GetAddressBytes();

            Array.Reverse(ipBytes);

            var msg = new MessageBuilder()
                      .Code(MessageCode.ServerConnectToPeer)
                      .WriteString(username)
                      .WriteString("P")
                      .WriteBytes(ipBytes)
                      .WriteInteger(port)
                      .WriteInteger(token)
                      .Build();

            using (var s = new SoulseekClient("127.0.0.1", 1, connectionManager: connMgr.Object))
            {
                s.InvokeMethod("ServerConnection_MessageRead", null, msg);

                Assert.Equal(username, response.Username);
                Assert.Equal(ip, response.IPAddress);
                Assert.Equal(port, response.Port);

                connMgr.Verify(m => m.GetOrAddSolicitedConnectionAsync(It.IsAny <ConnectToPeerResponse>(), It.IsAny <EventHandler <Message> >(), It.IsAny <ConnectionOptions>(), It.IsAny <CancellationToken>()), Times.Once);
            }
        }
Esempio n. 7
0
        public void Parse_Returns_Expected_Data(string username, string type, IPEndPoint endpoint, int token, bool isPrivileged)
        {
            var ipBytes = endpoint.Address.GetAddressBytes();

            Array.Reverse(ipBytes);

            var msg = new MessageBuilder()
                      .WriteCode(MessageCode.Server.ConnectToPeer)
                      .WriteString(username)
                      .WriteString(type)
                      .WriteBytes(ipBytes)
                      .WriteInteger(endpoint.Port)
                      .WriteInteger(token)
                      .WriteByte((byte)(isPrivileged ? 1 : 0))
                      .Build();

            var response = ConnectToPeerResponse.FromByteArray(msg);

            Assert.Equal(username, response.Username);
            Assert.Equal(type, response.Type);
            Assert.Equal(endpoint.Address, response.IPAddress);
            Assert.Equal(endpoint.Port, response.Port);
            Assert.Equal(token, response.Token);
            Assert.Equal(isPrivileged, response.IsPrivileged);
        }
Esempio n. 8
0
        internal async Task GetTransferConnectionAsync_Returns_Expected_IConnection(string username, string type, IPAddress ipAddress, int port, int token, ConnectionOptions options)
        {
            var conn = new Mock <IConnection>();

            conn.Setup(m => m.IPAddress)
            .Returns(ipAddress);
            conn.Setup(m => m.Port)
            .Returns(port);

            var connFactory = new Mock <IConnectionFactory>();

            connFactory.Setup(m => m.GetConnection(It.IsAny <IPAddress>(), It.IsAny <int>(), It.IsAny <ConnectionOptions>()))
            .Returns(conn.Object);

            var response = new ConnectToPeerResponse(username, type, ipAddress, port, token);

            var s = new SoulseekClient("127.0.0.1", 1, connectionFactory: connFactory.Object);

            IConnection result = null;

            var ex = await Record.ExceptionAsync(async() => result = await s.InvokeMethod <Task <IConnection> >("GetTransferConnectionAsync", response, options, CancellationToken.None));

            Assert.Null(ex);
            Assert.Equal(response.IPAddress, result.IPAddress);
            Assert.Equal(response.Port, result.Port);
        }
        public void Parse_Returns_Expected_Data()
        {
            var un   = RandomGuid;
            var type = RandomGuid;

            var ip      = new IPAddress(Random.Next(1024));
            var ipBytes = ip.GetAddressBytes();

            Array.Reverse(ipBytes);

            var port  = Random.Next();
            var token = Random.Next();

            var msg = new MessageBuilder()
                      .Code(MessageCode.ServerConnectToPeer)
                      .WriteString(un)
                      .WriteString(type)
                      .WriteBytes(ipBytes)
                      .WriteInteger(port)
                      .WriteInteger(token)
                      .Build();

            var response = ConnectToPeerResponse.Parse(msg);

            Assert.Equal(un, response.Username);
            Assert.Equal(type, response.Type);
            Assert.Equal(ip, response.IPAddress);
            Assert.Equal(port, response.Port);
            Assert.Equal(token, response.Token);
        }
Esempio n. 10
0
        public void Parse_Throws_MessageException_On_Code_Mismatch()
        {
            var msg = new MessageBuilder()
                      .WriteCode(MessageCode.Peer.BrowseRequest)
                      .Build();

            var ex = Record.Exception(() => ConnectToPeerResponse.FromByteArray(msg));

            Assert.NotNull(ex);
            Assert.IsType <MessageException>(ex);
        }
Esempio n. 11
0
        public void Parse_Throws_MessageReadException_On_Missing_Data()
        {
            var msg = new MessageBuilder()
                      .WriteCode(MessageCode.Server.ConnectToPeer)
                      .WriteString("foo")
                      .WriteString("F")
                      .Build();

            var ex = Record.Exception(() => ConnectToPeerResponse.FromByteArray(msg));

            Assert.NotNull(ex);
            Assert.IsType <MessageReadException>(ex);
        }
        public void Instantiates_With_The_Given_Data(string username, string type, IPEndPoint endpoint, int token)
        {
            ConnectToPeerResponse response = null;

            var ex = Record.Exception(() => response = new ConnectToPeerResponse(username, type, endpoint, token));

            Assert.Null(ex);

            Assert.Equal(username, response.Username);
            Assert.Equal(type, response.Type);
            Assert.Equal(endpoint.Address, response.IPEndPoint.Address);
            Assert.Equal(endpoint.Port, response.IPEndPoint.Port);
            Assert.Equal(token, response.Token);
        }
Esempio n. 13
0
        public async Task GetSolicitedPeerConnectionAsync_Adds_Instance_To_PeerConnectionManager(string username, IPAddress ipAddress, int port, int token)
        {
            var ctpr    = new ConnectToPeerResponse(username, "P", ipAddress, port, token);
            var options = new ConnectionOptions();

            var pcm = new Mock <IConnectionManager <IMessageConnection> >();

            pcm.Setup(m => m.AddAsync(It.IsAny <IMessageConnection>()))
            .Returns(Task.CompletedTask);

            var s = new SoulseekClient("127.0.0.1", 1, peerConnectionManager: pcm.Object);

            await s.InvokeMethod <Task <IMessageConnection> >("GetSolicitedPeerConnectionAsync", ctpr, options, CancellationToken.None);

            pcm.Verify(m => m.AddAsync(It.IsAny <IMessageConnection>()), Times.Once);
        }
        public async Task Does_Not_Throw_On_Download_Missing_From_ActiveDownloads()
        {
            var conn = new Mock <IConnection>();

            conn.Setup(m => m.ReadAsync(It.IsAny <int>(), It.IsAny <CancellationToken>()))
            .Returns(Task.FromResult(BitConverter.GetBytes(1)));

            var connManager = new Mock <IConnectionManager>();

            connManager.Setup(m => m.AddSolicitedTransferConnectionAsync(It.IsAny <ConnectToPeerResponse>(), It.IsAny <ConnectionOptions>(), It.IsAny <CancellationToken>()))
            .Returns(Task.FromResult(conn.Object));

            var s = new SoulseekClient("127.0.0.1", 1, null, connectionManager: connManager.Object);
            var r = new ConnectToPeerResponse("username", "F", IPAddress.Parse("127.0.0.1"), 1, 1);

            var ex = await Record.ExceptionAsync(async() => await s.InvokeMethod <Task>("InitializeDownloadAsync", r));

            Assert.Null(ex);
        }
Esempio n. 15
0
        public async Task GetSolicitedPeerConnectionAsync_Returns_IMessageConnection_Instance(string username, IPAddress ipAddress, int port, int token)
        {
            var ctpr    = new ConnectToPeerResponse(username, "P", ipAddress, port, token);
            var options = new ConnectionOptions();

            var s = new SoulseekClient();

            IMessageConnection conn = null;

            var ex = await Record.ExceptionAsync(async() => conn = await s.InvokeMethod <Task <IMessageConnection> >("GetSolicitedPeerConnectionAsync", ctpr, options, CancellationToken.None));

            Assert.Null(ex);
            Assert.NotNull(conn);

            Assert.Equal(username, conn.Username);
            Assert.Equal(ipAddress, conn.IPAddress);
            Assert.Equal(port, conn.Port);
            Assert.Equal(ctpr, conn.Context);
        }
        public void Instantiates_With_The_Given_Data()
        {
            var un    = RandomGuid;
            var type  = RandomGuid;
            var ip    = new IPAddress(Random.Next(1024));
            var port  = Random.Next();
            var token = Random.Next();

            ConnectToPeerResponse response = null;

            var ex = Record.Exception(() => response = new ConnectToPeerResponse(un, type, ip, port, token));

            Assert.Null(ex);

            Assert.Equal(un, response.Username);
            Assert.Equal(type, response.Type);
            Assert.Equal(ip, response.IPAddress);
            Assert.Equal(port, response.Port);
            Assert.Equal(token, response.Token);
        }
        internal async Task GetOrAddSolicitedConnectionAsync_Connects_And_Pierces_Firewall(
            string username, IPAddress ipAddress, int port, EventHandler <Message> messageHandler, ConnectionOptions options, int token)
        {
            var ctpr = new ConnectToPeerResponse(username, "P", ipAddress, port, token);

            var expectedBytes = new PierceFirewallRequest(token).ToMessage().ToByteArray();

            byte[] actualBytes = Array.Empty <byte>();

            var tokenFactory = new Mock <ITokenFactory>();

            tokenFactory.Setup(m => m.GetToken())
            .Returns(token);

            var conn = new Mock <IMessageConnection>();

            conn.Setup(m => m.ConnectAsync(It.IsAny <CancellationToken>()))
            .Returns(Task.CompletedTask);
            conn.Setup(m => m.WriteAsync(It.IsAny <byte[]>(), It.IsAny <CancellationToken>()))
            .Returns(Task.CompletedTask)
            .Callback <byte[], CancellationToken>((b, ct) => actualBytes = b);

            var connFactory = new Mock <IConnectionFactory>();

            connFactory.Setup(m => m.GetMessageConnection(MessageConnectionType.Peer, username, ipAddress, port, options))
            .Returns(conn.Object);

            var c = new ConnectionManager(10, tokenFactory.Object, connFactory.Object);

            IMessageConnection connection = null;

            var ex = await Record.ExceptionAsync(async() => connection = await c.GetOrAddSolicitedConnectionAsync(ctpr, messageHandler, options, CancellationToken.None));

            Assert.Null(ex);

            Assert.Equal(conn.Object, connection);

            Assert.Equal(expectedBytes, actualBytes);
        }
        internal async Task AddSolicitedTransferConnectionAsync_Connects_And_Pierces_Firewall(string username, IPAddress ipAddress, int port, int token, ConnectionOptions options)
        {
            var ctpr          = new ConnectToPeerResponse(username, "F", ipAddress, port, token);
            var expectedBytes = new PierceFirewallRequest(token).ToMessage().ToByteArray();

            byte[] actualBytes = Array.Empty <byte>();

            var conn = new Mock <IConnection>();

            conn.Setup(m => m.IPAddress)
            .Returns(ipAddress);
            conn.Setup(m => m.Port)
            .Returns(port);
            conn.Setup(m => m.ConnectAsync(It.IsAny <CancellationToken>()))
            .Returns(Task.CompletedTask);
            conn.Setup(m => m.WriteAsync(It.IsAny <byte[]>(), It.IsAny <CancellationToken>()))
            .Returns(Task.CompletedTask)
            .Callback <byte[], CancellationToken>((b, c) => actualBytes = b);

            var connFactory = new Mock <IConnectionFactory>();

            connFactory.Setup(m => m.GetConnection(It.IsAny <IPAddress>(), It.IsAny <int>(), It.IsAny <ConnectionOptions>()))
            .Returns(conn.Object);

            IConnection newConn = null;

            using (var c = new ConnectionManager(1, connectionFactory: connFactory.Object))
            {
                newConn = await c.AddSolicitedTransferConnectionAsync(ctpr, options, CancellationToken.None);
            }

            Assert.Equal(ipAddress, newConn.IPAddress);
            Assert.Equal(port, newConn.Port);

            Assert.Equal(expectedBytes, actualBytes);

            conn.Verify(m => m.ConnectAsync(It.IsAny <CancellationToken>()), Times.Once);
            conn.Verify(m => m.WriteAsync(It.IsAny <byte[]>(), It.IsAny <CancellationToken>()), Times.Once);
        }
        internal async Task GetOrAddSolicitedConnectionAsync_Returns_Existing_Connection(
            string username, IPAddress ipAddress, int port, EventHandler <Message> messageHandler, ConnectionOptions options, int token)
        {
            var ctpr = new ConnectToPeerResponse(username, "P", ipAddress, port, token);

            var key  = new ConnectionKey(username, ipAddress, port, MessageConnectionType.Peer);
            var conn = new Mock <IMessageConnection>();

            var peer = new ConcurrentDictionary <ConnectionKey, (SemaphoreSlim Semaphore, IMessageConnection Connection)>();

            peer.GetOrAdd(key, (new SemaphoreSlim(1), conn.Object));

            var c = new ConnectionManager(10);

            c.SetProperty("PeerConnections", peer);

            IMessageConnection connection = null;

            var ex = await Record.ExceptionAsync(async() => connection = await c.GetOrAddSolicitedConnectionAsync(ctpr, messageHandler, options, CancellationToken.None));

            Assert.Null(ex);

            Assert.Equal(conn.Object, connection);
        }
Esempio n. 20
0
        /// <summary>
        ///     Handles incoming messages.
        /// </summary>
        /// <param name="sender">The <see cref="IMessageConnection"/> instance from which the message originated.</param>
        /// <param name="message">The message.</param>
        public async void HandleMessageRead(object sender, byte[] message)
        {
            var code = new MessageReader <MessageCode.Server>(message).ReadCode();

            if (code != MessageCode.Server.SearchRequest)
            {
                Diagnostic.Debug($"Server message received: {code}");
            }

            try
            {
                switch (code)
                {
                case MessageCode.Server.ParentMinSpeed:
                case MessageCode.Server.ParentSpeedRatio:
                case MessageCode.Server.WishlistInterval:
                case MessageCode.Server.CheckPrivileges:
                    SoulseekClient.Waiter.Complete(new WaitKey(code), IntegerResponse.FromByteArray <MessageCode.Server>(message));
                    break;

                case MessageCode.Server.NewPassword:
                    var confirmedPassword = NewPassword.FromByteArray(message).Password;
                    SoulseekClient.Waiter.Complete(new WaitKey(code), confirmedPassword);
                    break;

                case MessageCode.Server.GlobalAdminMessage:
                    var msg = GlobalMessageNotification.FromByteArray(message);
                    GlobalMessageReceived?.Invoke(this, new GlobalMessageReceivedEventArgs(msg));
                    break;

                case MessageCode.Server.Ping:
                    SoulseekClient.Waiter.Complete(new WaitKey(code));
                    break;

                case MessageCode.Server.Login:
                    SoulseekClient.Waiter.Complete(new WaitKey(code), LoginResponse.FromByteArray(message));
                    break;

                case MessageCode.Server.RoomList:
                    var roomList = RoomListResponse.FromByteArray(message);
                    SoulseekClient.Waiter.Complete(new WaitKey(code), roomList);
                    RoomListReceived?.Invoke(this, new RoomListReceivedEventArgs(roomList));
                    break;

                case MessageCode.Server.PrivilegedUsers:
                    var privilegedUserList = PrivilegedUserListNotification.FromByteArray(message);
                    SoulseekClient.Waiter.Complete(new WaitKey(code), privilegedUserList);
                    PrivilegedUserListReceived?.Invoke(this, new PrivilegedUserListReceivedEventArgs(privilegedUserList));
                    break;

                case MessageCode.Server.AddPrivilegedUser:
                    PrivilegeNotificationReceived?.Invoke(this, new PrivilegeNotificationReceivedEventArgs(PrivilegedUserNotification.FromByteArray(message)));
                    break;

                case MessageCode.Server.NotifyPrivileges:
                    var pn = PrivilegeNotification.FromByteArray(message);
                    PrivilegeNotificationReceived?.Invoke(this, new PrivilegeNotificationReceivedEventArgs(pn.Username, pn.Id));

                    if (SoulseekClient.Options.AutoAcknowledgePrivilegeNotifications)
                    {
                        await SoulseekClient.AcknowledgePrivilegeNotificationAsync(pn.Id, CancellationToken.None).ConfigureAwait(false);
                    }

                    break;

                case MessageCode.Server.UserPrivileges:
                    var privilegeResponse = UserPrivilegeResponse.FromByteArray(message);
                    SoulseekClient.Waiter.Complete(new WaitKey(code, privilegeResponse.Username), privilegeResponse.IsPrivileged);
                    break;

                case MessageCode.Server.NetInfo:
                    var netInfo = NetInfoNotification.FromByteArray(message);

                    try
                    {
                        var parents = netInfo.Parents.Select(parent => (parent.Username, new IPEndPoint(parent.IPAddress, parent.Port)));
                        await SoulseekClient.DistributedConnectionManager.AddParentConnectionAsync(parents).ConfigureAwait(false);
                    }
                    catch (Exception ex)
                    {
                        Diagnostic.Debug($"Error handling NetInfo message: {ex.Message}");
                    }

                    break;

                case MessageCode.Server.ConnectToPeer:
                    ConnectToPeerResponse connectToPeerResponse = default;

                    try
                    {
                        connectToPeerResponse = ConnectToPeerResponse.FromByteArray(message);

                        if (connectToPeerResponse.Type == Constants.ConnectionType.Transfer)
                        {
                            Diagnostic.Debug($"Received transfer ConnectToPeer request from {connectToPeerResponse.Username} ({connectToPeerResponse.IPEndPoint}) for remote token {connectToPeerResponse.Token}");

                            // ensure that we are expecting at least one file from this user before we connect. the response
                            // doesn't contain any other identifying information about the file.
                            if (!SoulseekClient.Downloads.IsEmpty && SoulseekClient.Downloads.Values.Any(d => d.Username == connectToPeerResponse.Username))
                            {
                                var(connection, remoteToken) = await SoulseekClient.PeerConnectionManager.GetTransferConnectionAsync(connectToPeerResponse).ConfigureAwait(false);

                                var download = SoulseekClient.Downloads.Values.FirstOrDefault(v => v.RemoteToken == remoteToken && v.Username == connectToPeerResponse.Username);

                                if (download != default(TransferInternal))
                                {
                                    Diagnostic.Debug($"Solicited inbound transfer connection to {download.Username} ({connection.IPEndPoint}) for token {download.Token} (remote: {download.RemoteToken}) established. (id: {connection.Id})");
                                    SoulseekClient.Waiter.Complete(new WaitKey(Constants.WaitKey.IndirectTransfer, download.Username, download.Filename, download.RemoteToken), connection);
                                }
                                else
                                {
                                    Diagnostic.Debug($"Transfer ConnectToPeer request from {connectToPeerResponse.Username} ({connectToPeerResponse.IPEndPoint}) for remote token {connectToPeerResponse.Token} does not match any waiting downloads, discarding.");
                                    connection.Disconnect($"Unknown transfer");
                                }
                            }
                            else
                            {
                                throw new SoulseekClientException($"Unexpected transfer request from {connectToPeerResponse.Username} ({connectToPeerResponse.IPEndPoint}); Ignored");
                            }
                        }
                        else if (connectToPeerResponse.Type == Constants.ConnectionType.Peer)
                        {
                            Diagnostic.Debug($"Received message ConnectToPeer request from {connectToPeerResponse.Username} ({connectToPeerResponse.IPEndPoint})");
                            await SoulseekClient.PeerConnectionManager.GetOrAddMessageConnectionAsync(connectToPeerResponse).ConfigureAwait(false);
                        }
                        else if (connectToPeerResponse.Type == Constants.ConnectionType.Distributed)
                        {
                            Diagnostic.Debug($"Received distributed ConnectToPeer request from {connectToPeerResponse.Username} ({connectToPeerResponse.IPEndPoint})");
                            await SoulseekClient.DistributedConnectionManager.AddChildConnectionAsync(connectToPeerResponse).ConfigureAwait(false);
                        }
                        else
                        {
                            throw new MessageException($"Unknown Connect To Peer connection type '{connectToPeerResponse.Type}'");
                        }
                    }
                    catch (Exception ex)
                    {
                        Diagnostic.Debug($"Error handling ConnectToPeer response from {connectToPeerResponse?.Username} ({connectToPeerResponse?.IPEndPoint}): {ex.Message}");
                    }

                    break;

                case MessageCode.Server.AddUser:
                    var addUserResponse = AddUserResponse.FromByteArray(message);
                    SoulseekClient.Waiter.Complete(new WaitKey(code, addUserResponse.Username), addUserResponse);
                    break;

                case MessageCode.Server.GetStatus:
                    var statsResponse = UserStatusResponse.FromByteArray(message);
                    SoulseekClient.Waiter.Complete(new WaitKey(code, statsResponse.Username), statsResponse);
                    UserStatusChanged?.Invoke(this, new UserStatusChangedEventArgs(statsResponse));
                    break;

                case MessageCode.Server.PrivateMessage:
                    var pm = PrivateMessageNotification.FromByteArray(message);
                    PrivateMessageReceived?.Invoke(this, new PrivateMessageReceivedEventArgs(pm));

                    if (SoulseekClient.Options.AutoAcknowledgePrivateMessages)
                    {
                        await SoulseekClient.AcknowledgePrivateMessageAsync(pm.Id, CancellationToken.None).ConfigureAwait(false);
                    }

                    break;

                case MessageCode.Server.GetPeerAddress:
                    var peerAddressResponse = UserAddressResponse.FromByteArray(message);
                    SoulseekClient.Waiter.Complete(new WaitKey(code, peerAddressResponse.Username), peerAddressResponse);
                    break;

                case MessageCode.Server.JoinRoom:
                    var roomData = RoomJoinResponse.FromByteArray(message);
                    SoulseekClient.Waiter.Complete(new WaitKey(code, roomData.Name), roomData);
                    break;

                case MessageCode.Server.LeaveRoom:
                    var leaveRoomResponse = RoomLeaveResponse.FromByteArray(message);
                    SoulseekClient.Waiter.Complete(new WaitKey(code, leaveRoomResponse.RoomName));
                    break;

                case MessageCode.Server.SayInChatRoom:
                    var roomMessage = RoomMessageNotification.FromByteArray(message);
                    RoomMessageReceived?.Invoke(this, new RoomMessageReceivedEventArgs(roomMessage));
                    break;

                case MessageCode.Server.UserJoinedRoom:
                    var joinNotification = RoomJoinedNotification.FromByteArray(message);
                    RoomJoined?.Invoke(this, new RoomJoinedEventArgs(joinNotification));
                    break;

                case MessageCode.Server.UserLeftRoom:
                    var leftNotification = RoomLeftNotification.FromByteArray(message);
                    RoomLeft?.Invoke(this, new RoomLeftEventArgs(leftNotification));
                    break;

                case MessageCode.Server.KickedFromServer:
                    KickedFromServer?.Invoke(this, EventArgs.Empty);
                    break;

                case MessageCode.Server.FileSearch:
                    var searchRequest = ServerSearchRequest.FromByteArray(message);

                    // sometimes (most of the time?) a room search will result in a request to ourselves (assuming we are
                    // joined to it)
                    if (searchRequest.Username == SoulseekClient.Username)
                    {
                        break;
                    }

                    SearchResponse searchResponse;

                    if (SoulseekClient.Options.SearchResponseResolver == default)
                    {
                        break;
                    }

                    try
                    {
                        searchResponse = await SoulseekClient.Options.SearchResponseResolver(searchRequest.Username, searchRequest.Token, SearchQuery.FromText(searchRequest.Query)).ConfigureAwait(false);

                        if (searchResponse != null && searchResponse.FileCount + searchResponse.LockedFileCount > 0)
                        {
                            var endpoint = await SoulseekClient.GetUserEndPointAsync(searchRequest.Username).ConfigureAwait(false);

                            var peerConnection = await SoulseekClient.PeerConnectionManager.GetOrAddMessageConnectionAsync(searchRequest.Username, endpoint, CancellationToken.None).ConfigureAwait(false);

                            await peerConnection.WriteAsync(searchResponse.ToByteArray()).ConfigureAwait(false);
                        }
                    }
                    catch (Exception ex)
                    {
                        Diagnostic.Warning($"Error resolving search response for query '{searchRequest.Query}' requested by {searchRequest.Username} with token {searchRequest.Token}: {ex.Message}", ex);
                    }

                    break;

                // if we fail to connect to a distributed parent in a timely manner, the server will begin to send us distributed search requests directly.
                // forward these to the distributed message handler.
                case MessageCode.Server.SearchRequest:
                    SoulseekClient.DistributedMessageHandler.HandleMessageRead(SoulseekClient.ServerConnection, message);
                    break;

                default:
                    Diagnostic.Debug($"Unhandled server message: {code}; {message.Length} bytes");
                    break;
                }
            }
            catch (Exception ex)
            {
                Diagnostic.Warning($"Error handling server message: {code}; {ex.Message}", ex);
            }
        }
Esempio n. 21
0
        /// <summary>
        ///     Handles incoming messages.
        /// </summary>
        /// <param name="sender">The <see cref="IMessageConnection"/> instance from which the message originated.</param>
        /// <param name="message">The message.</param>
        public async void HandleMessage(object sender, byte[] message)
        {
            var code = new MessageReader <MessageCode.Server>(message).ReadCode();

            Diagnostic.Debug($"Server message received: {code}");

            try
            {
                switch (code)
                {
                case MessageCode.Server.ParentMinSpeed:
                case MessageCode.Server.ParentSpeedRatio:
                case MessageCode.Server.WishlistInterval:
                    SoulseekClient.Waiter.Complete(new WaitKey(code), IntegerResponse.FromByteArray <MessageCode.Server>(message));
                    break;

                case MessageCode.Server.Login:
                    SoulseekClient.Waiter.Complete(new WaitKey(code), LoginResponse.FromByteArray(message));
                    break;

                case MessageCode.Server.RoomList:
                    SoulseekClient.Waiter.Complete(new WaitKey(code), RoomList.FromByteArray(message));
                    break;

                case MessageCode.Server.PrivilegedUsers:
                    SoulseekClient.Waiter.Complete(new WaitKey(code), PrivilegedUserList.FromByteArray(message));
                    break;

                case MessageCode.Server.NetInfo:
                    var netInfo = NetInfo.FromByteArray(message);

                    try
                    {
                        await SoulseekClient.DistributedConnectionManager.AddParentConnectionAsync(netInfo.Parents).ConfigureAwait(false);
                    }
                    catch (Exception ex)
                    {
                        Diagnostic.Debug($"Error handling NetInfo message: {ex.Message}");
                    }

                    break;

                case MessageCode.Server.ConnectToPeer:
                    ConnectToPeerResponse connectToPeerResponse = default;

                    try
                    {
                        connectToPeerResponse = ConnectToPeerResponse.FromByteArray(message);

                        if (connectToPeerResponse.Type == Constants.ConnectionType.Transfer)
                        {
                            // ensure that we are expecting at least one file from this user before we connect. the response
                            // doesn't contain any other identifying information about the file.
                            if (!SoulseekClient.Downloads.IsEmpty && SoulseekClient.Downloads.Values.Any(d => d.Username == connectToPeerResponse.Username))
                            {
                                var(connection, remoteToken) = await SoulseekClient.PeerConnectionManager.GetTransferConnectionAsync(connectToPeerResponse).ConfigureAwait(false);

                                var download = SoulseekClient.Downloads.Values.FirstOrDefault(v => v.RemoteToken == remoteToken && v.Username == connectToPeerResponse.Username);

                                if (download != default(Transfer))
                                {
                                    SoulseekClient.Waiter.Complete(new WaitKey(Constants.WaitKey.IndirectTransfer, download.Username, download.Filename, download.RemoteToken), connection);
                                }
                            }
                            else
                            {
                                throw new SoulseekClientException($"Unexpected transfer request from {connectToPeerResponse.Username} ({connectToPeerResponse.IPAddress}:{connectToPeerResponse.Port}); Ignored");
                            }
                        }
                        else if (connectToPeerResponse.Type == Constants.ConnectionType.Peer)
                        {
                            await SoulseekClient.PeerConnectionManager.GetOrAddMessageConnectionAsync(connectToPeerResponse).ConfigureAwait(false);
                        }
                        else if (connectToPeerResponse.Type == Constants.ConnectionType.Distributed)
                        {
                            await SoulseekClient.DistributedConnectionManager.AddChildConnectionAsync(connectToPeerResponse).ConfigureAwait(false);
                        }
                        else
                        {
                            throw new MessageException($"Unknown Connect To Peer connection type '{connectToPeerResponse.Type}'");
                        }
                    }
                    catch (Exception ex)
                    {
                        Diagnostic.Debug($"Error handling ConnectToPeer response from {connectToPeerResponse?.Username} ({connectToPeerResponse?.IPAddress}:{connectToPeerResponse.Port}): {ex.Message}");
                    }

                    break;

                case MessageCode.Server.AddUser:
                    var addUserResponse = AddUserResponse.FromByteArray(message);
                    SoulseekClient.Waiter.Complete(new WaitKey(code, addUserResponse.Username), addUserResponse);
                    break;

                case MessageCode.Server.GetStatus:
                    var statsResponse = GetStatusResponse.FromByteArray(message);
                    SoulseekClient.Waiter.Complete(new WaitKey(code, statsResponse.Username), statsResponse);
                    UserStatusChanged?.Invoke(this, new UserStatusChangedEventArgs(statsResponse));
                    break;

                case MessageCode.Server.PrivateMessage:
                    var pm = PrivateMessage.FromByteArray(message);
                    PrivateMessageReceived?.Invoke(this, pm);

                    if (SoulseekClient.Options.AutoAcknowledgePrivateMessages)
                    {
                        await SoulseekClient.AcknowledgePrivateMessageAsync(pm.Id, CancellationToken.None).ConfigureAwait(false);
                    }

                    break;

                case MessageCode.Server.GetPeerAddress:
                    var peerAddressResponse = GetPeerAddressResponse.FromByteArray(message);
                    SoulseekClient.Waiter.Complete(new WaitKey(code, peerAddressResponse.Username), peerAddressResponse);
                    break;

                default:
                    Diagnostic.Debug($"Unhandled server message: {code}; {message.Length} bytes");
                    break;
                }
            }
            catch (Exception ex)
            {
                Diagnostic.Warning($"Error handling server message: {code}; {ex.Message}", ex);
            }
        }
Esempio n. 22
0
        /// <summary>
        ///     Handles incoming messages.
        /// </summary>
        /// <param name="sender">The <see cref="IMessageConnection"/> instance from which the message originated.</param>
        /// <param name="message">The message.</param>
        public async void HandleMessageRead(object sender, byte[] message)
        {
            var code = new MessageReader <MessageCode.Server>(message).ReadCode();

            if (code != MessageCode.Server.EmbeddedMessage)
            {
                Diagnostic.Debug($"Server message received: {code}");
            }

            try
            {
                switch (code)
                {
                case MessageCode.Server.ParentMinSpeed:
                case MessageCode.Server.ParentSpeedRatio:
                case MessageCode.Server.WishlistInterval:
                case MessageCode.Server.CheckPrivileges:
                    SoulseekClient.Waiter.Complete(new WaitKey(code), IntegerResponse.FromByteArray <MessageCode.Server>(message));
                    break;

                case MessageCode.Server.PrivateRoomAdded:
                    PrivateRoomMembershipAdded?.Invoke(this, StringResponse.FromByteArray <MessageCode.Server>(message));
                    break;

                case MessageCode.Server.PrivateRoomRemoved:
                    var privateRoomRemoved = StringResponse.FromByteArray <MessageCode.Server>(message);
                    SoulseekClient.Waiter.Complete(new WaitKey(code, privateRoomRemoved));
                    PrivateRoomMembershipRemoved?.Invoke(this, privateRoomRemoved);
                    break;

                case MessageCode.Server.PrivateRoomOperatorAdded:
                    PrivateRoomModerationAdded?.Invoke(this, StringResponse.FromByteArray <MessageCode.Server>(message));
                    break;

                case MessageCode.Server.PrivateRoomOperatorRemoved:
                    var privateRoomOperatorRemoved = StringResponse.FromByteArray <MessageCode.Server>(message);
                    SoulseekClient.Waiter.Complete(new WaitKey(code, privateRoomOperatorRemoved));
                    PrivateRoomModerationRemoved?.Invoke(this, privateRoomOperatorRemoved);
                    break;

                case MessageCode.Server.NewPassword:
                    var confirmedPassword = NewPassword.FromByteArray(message).Password;
                    SoulseekClient.Waiter.Complete(new WaitKey(code), confirmedPassword);
                    break;

                case MessageCode.Server.PrivateRoomToggle:
                    var acceptInvitations = PrivateRoomToggle.FromByteArray(message).AcceptInvitations;
                    SoulseekClient.Waiter.Complete(new WaitKey(code), acceptInvitations);
                    break;

                case MessageCode.Server.GlobalAdminMessage:
                    var msg = GlobalMessageNotification.FromByteArray(message);
                    GlobalMessageReceived?.Invoke(this, msg);
                    break;

                case MessageCode.Server.Ping:
                    SoulseekClient.Waiter.Complete(new WaitKey(code));
                    break;

                case MessageCode.Server.Login:
                    SoulseekClient.Waiter.Complete(new WaitKey(code), LoginResponse.FromByteArray(message));
                    break;

                case MessageCode.Server.RoomList:
                    var roomList = RoomListResponse.FromByteArray(message);
                    SoulseekClient.Waiter.Complete(new WaitKey(code), roomList);
                    RoomListReceived?.Invoke(this, roomList);
                    break;

                case MessageCode.Server.PrivateRoomOwned:
                    var moderatedRoomInfo = PrivateRoomOwnedListNotification.FromByteArray(message);
                    PrivateRoomModeratedUserListReceived?.Invoke(this, moderatedRoomInfo);
                    break;

                case MessageCode.Server.PrivateRoomUsers:
                    var roomInfo = PrivateRoomUserListNotification.FromByteArray(message);
                    PrivateRoomUserListReceived?.Invoke(this, roomInfo);
                    break;

                case MessageCode.Server.PrivilegedUsers:
                    var privilegedUserList = PrivilegedUserListNotification.FromByteArray(message);
                    SoulseekClient.Waiter.Complete(new WaitKey(code), privilegedUserList);
                    PrivilegedUserListReceived?.Invoke(this, privilegedUserList);
                    break;

                case MessageCode.Server.AddPrivilegedUser:
                    PrivilegeNotificationReceived?.Invoke(this, new PrivilegeNotificationReceivedEventArgs(PrivilegedUserNotification.FromByteArray(message)));
                    break;

                case MessageCode.Server.NotifyPrivileges:
                    var pn = PrivilegeNotification.FromByteArray(message);
                    PrivilegeNotificationReceived?.Invoke(this, new PrivilegeNotificationReceivedEventArgs(pn.Username, pn.Id));

                    if (SoulseekClient.Options.AutoAcknowledgePrivilegeNotifications)
                    {
                        await SoulseekClient.AcknowledgePrivilegeNotificationAsync(pn.Id, CancellationToken.None).ConfigureAwait(false);
                    }

                    break;

                case MessageCode.Server.UserPrivileges:
                    var privilegeResponse = UserPrivilegeResponse.FromByteArray(message);
                    SoulseekClient.Waiter.Complete(new WaitKey(code, privilegeResponse.Username), privilegeResponse.IsPrivileged);
                    break;

                case MessageCode.Server.NetInfo:
                    var netInfo = NetInfoNotification.FromByteArray(message);

                    try
                    {
                        var parents = netInfo.Parents.Select(parent => (parent.Username, new IPEndPoint(parent.IPAddress, parent.Port)));
                        await SoulseekClient.DistributedConnectionManager.AddParentConnectionAsync(parents).ConfigureAwait(false);
                    }
                    catch (Exception ex)
                    {
                        Diagnostic.Debug($"Error handling NetInfo message: {ex.Message}");
                    }

                    break;

                case MessageCode.Server.CannotConnect:
                    var cannotConnect = CannotConnect.FromByteArray(message);
                    Diagnostic.Debug($"Received CannotConnect message for token {cannotConnect.Token}{(!string.IsNullOrEmpty(cannotConnect.Username) ? $" from user {cannotConnect.Username}" : string.Empty)}");

                    SoulseekClient.SearchResponder.TryDiscard(cannotConnect.Token);

                    if (!string.IsNullOrEmpty(cannotConnect.Username))
                    {
                        UserCannotConnect?.Invoke(this, new UserCannotConnectEventArgs(cannotConnect));
                    }

                    break;

                case MessageCode.Server.CannotJoinRoom:
                    var cannotJoinRoom = CannotJoinRoom.FromByteArray(message);
                    SoulseekClient.Waiter.Throw(
                        new WaitKey(MessageCode.Server.JoinRoom, cannotJoinRoom.RoomName),
                        new RoomJoinForbiddenException($"The server rejected the request to join room {cannotJoinRoom.RoomName}"));

                    break;

                case MessageCode.Server.ConnectToPeer:
                    var connectToPeerResponse = ConnectToPeerResponse.FromByteArray(message);

                    try
                    {
                        if (connectToPeerResponse.Type == Constants.ConnectionType.Transfer)
                        {
                            Diagnostic.Debug($"Received transfer ConnectToPeer request from {connectToPeerResponse.Username} ({connectToPeerResponse.IPEndPoint}) for remote token {connectToPeerResponse.Token}");

                            // ensure that we are expecting at least one file from this user before we connect. the response
                            // doesn't contain any other identifying information about the file.
                            if (!SoulseekClient.Downloads.IsEmpty && SoulseekClient.Downloads.Values.Any(d => d.Username == connectToPeerResponse.Username))
                            {
                                var(connection, remoteToken) = await SoulseekClient.PeerConnectionManager.GetTransferConnectionAsync(connectToPeerResponse).ConfigureAwait(false);

                                var download = SoulseekClient.Downloads.Values.FirstOrDefault(v => v.RemoteToken == remoteToken && v.Username == connectToPeerResponse.Username);

                                if (download != default(TransferInternal))
                                {
                                    Diagnostic.Debug($"Solicited inbound transfer connection to {download.Username} ({connection.IPEndPoint}) for token {download.Token} (remote: {download.RemoteToken}) established. (id: {connection.Id})");
                                    SoulseekClient.Waiter.Complete(new WaitKey(Constants.WaitKey.IndirectTransfer, download.Username, download.Filename, download.RemoteToken), connection);
                                }
                                else
                                {
                                    Diagnostic.Debug($"Transfer ConnectToPeer request from {connectToPeerResponse.Username} ({connectToPeerResponse.IPEndPoint}) for remote token {connectToPeerResponse.Token} does not match any waiting downloads, discarding.");
                                    connection.Disconnect("Unknown transfer");
                                }
                            }
                            else
                            {
                                throw new SoulseekClientException($"Unexpected transfer request from {connectToPeerResponse.Username} ({connectToPeerResponse.IPEndPoint}); Ignored");
                            }
                        }
                        else if (connectToPeerResponse.Type == Constants.ConnectionType.Peer)
                        {
                            Diagnostic.Debug($"Received message ConnectToPeer request from {connectToPeerResponse.Username} ({connectToPeerResponse.IPEndPoint})");
                            await SoulseekClient.PeerConnectionManager.GetOrAddMessageConnectionAsync(connectToPeerResponse).ConfigureAwait(false);
                        }
                        else if (connectToPeerResponse.Type == Constants.ConnectionType.Distributed)
                        {
                            Diagnostic.Debug($"Received distributed ConnectToPeer request from {connectToPeerResponse.Username} ({connectToPeerResponse.IPEndPoint})");
                            await SoulseekClient.DistributedConnectionManager.GetOrAddChildConnectionAsync(connectToPeerResponse).ConfigureAwait(false);
                        }
                        else
                        {
                            throw new MessageException($"Unknown Connect To Peer connection type '{connectToPeerResponse.Type}'");
                        }
                    }
                    catch (Exception ex)
                    {
                        Diagnostic.Debug($"Error handling ConnectToPeer response from {connectToPeerResponse.Username} ({connectToPeerResponse.IPEndPoint}): {ex.Message}");
                    }

                    break;

                case MessageCode.Server.AddUser:
                    var addUserResponse = AddUserResponse.FromByteArray(message);
                    SoulseekClient.Waiter.Complete(new WaitKey(code, addUserResponse.Username), addUserResponse);
                    break;

                case MessageCode.Server.GetStatus:
                    var statsResponse = UserStatusResponse.FromByteArray(message);
                    SoulseekClient.Waiter.Complete(new WaitKey(code, statsResponse.Username), statsResponse);
                    UserStatusChanged?.Invoke(this, new UserStatusChangedEventArgs(statsResponse));
                    break;

                case MessageCode.Server.PrivateMessage:
                    var pm = PrivateMessageNotification.FromByteArray(message);
                    PrivateMessageReceived?.Invoke(this, new PrivateMessageReceivedEventArgs(pm));

                    if (SoulseekClient.Options.AutoAcknowledgePrivateMessages)
                    {
                        await SoulseekClient.AcknowledgePrivateMessageAsync(pm.Id, CancellationToken.None).ConfigureAwait(false);
                    }

                    break;

                case MessageCode.Server.GetPeerAddress:
                    var peerAddressResponse = UserAddressResponse.FromByteArray(message);
                    SoulseekClient.Waiter.Complete(new WaitKey(code, peerAddressResponse.Username), peerAddressResponse);
                    break;

                case MessageCode.Server.JoinRoom:
                    var roomData = JoinRoomResponse.FromByteArray(message);
                    SoulseekClient.Waiter.Complete(new WaitKey(code, roomData.Name), roomData);
                    break;

                case MessageCode.Server.LeaveRoom:
                    var leaveRoomResponse = LeaveRoomResponse.FromByteArray(message);
                    SoulseekClient.Waiter.Complete(new WaitKey(code, leaveRoomResponse.RoomName));
                    break;

                case MessageCode.Server.SayInChatRoom:
                    var roomMessage = RoomMessageNotification.FromByteArray(message);
                    RoomMessageReceived?.Invoke(this, new RoomMessageReceivedEventArgs(roomMessage));
                    break;

                case MessageCode.Server.PublicChat:
                    var publicChatMessage = PublicChatMessageNotification.FromByteArray(message);
                    PublicChatMessageReceived?.Invoke(this, new PublicChatMessageReceivedEventArgs(publicChatMessage));
                    break;

                case MessageCode.Server.UserJoinedRoom:
                    var joinNotification = UserJoinedRoomNotification.FromByteArray(message);
                    RoomJoined?.Invoke(this, new RoomJoinedEventArgs(joinNotification));
                    break;

                case MessageCode.Server.UserLeftRoom:
                    var leftNotification = UserLeftRoomNotification.FromByteArray(message);
                    RoomLeft?.Invoke(this, new RoomLeftEventArgs(leftNotification));
                    break;

                case MessageCode.Server.RoomTickers:
                    var roomTickers = RoomTickerListNotification.FromByteArray(message);
                    RoomTickerListReceived?.Invoke(this, new RoomTickerListReceivedEventArgs(roomTickers));
                    break;

                case MessageCode.Server.RoomTickerAdd:
                    var roomTickerAdded = RoomTickerAddedNotification.FromByteArray(message);
                    RoomTickerAdded?.Invoke(this, new RoomTickerAddedEventArgs(roomTickerAdded.RoomName, roomTickerAdded.Ticker));
                    break;

                case MessageCode.Server.RoomTickerRemove:
                    var roomTickerRemoved = RoomTickerRemovedNotification.FromByteArray(message);
                    RoomTickerRemoved?.Invoke(this, new RoomTickerRemovedEventArgs(roomTickerRemoved.RoomName, roomTickerRemoved.Username));
                    break;

                case MessageCode.Server.PrivateRoomAddUser:
                    var privateRoomAddUserResponse = PrivateRoomAddUser.FromByteArray(message);
                    SoulseekClient.Waiter.Complete(new WaitKey(code, privateRoomAddUserResponse.RoomName, privateRoomAddUserResponse.Username));
                    break;

                case MessageCode.Server.PrivateRoomRemoveUser:
                    var privateRoomRemoveUserResponse = PrivateRoomRemoveUser.FromByteArray(message);
                    SoulseekClient.Waiter.Complete(new WaitKey(code, privateRoomRemoveUserResponse.RoomName, privateRoomRemoveUserResponse.Username));
                    break;

                case MessageCode.Server.PrivateRoomAddOperator:
                    var privateRoomAddOperatorResponse = PrivateRoomAddOperator.FromByteArray(message);
                    SoulseekClient.Waiter.Complete(new WaitKey(code, privateRoomAddOperatorResponse.RoomName, privateRoomAddOperatorResponse.Username));
                    break;

                case MessageCode.Server.PrivateRoomRemoveOperator:
                    var privateRoomRemoveOperatorResponse = PrivateRoomRemoveOperator.FromByteArray(message);
                    SoulseekClient.Waiter.Complete(new WaitKey(code, privateRoomRemoveOperatorResponse.RoomName, privateRoomRemoveOperatorResponse.Username));
                    break;

                case MessageCode.Server.KickedFromServer:
                    KickedFromServer?.Invoke(this, EventArgs.Empty);
                    break;

                case MessageCode.Server.FileSearch:
                    var searchRequest = ServerSearchRequest.FromByteArray(message);

                    // sometimes (most of the time?) a room search will result in a request to ourselves (assuming we are
                    // joined to it)
                    if (searchRequest.Username == SoulseekClient.Username)
                    {
                        break;
                    }

                    await SoulseekClient.SearchResponder.TryRespondAsync(searchRequest.Username, searchRequest.Token, searchRequest.Query).ConfigureAwait(false);

                    break;

                case MessageCode.Server.EmbeddedMessage:
                    SoulseekClient.DistributedMessageHandler.HandleEmbeddedMessage(message);
                    break;

                default:
                    Diagnostic.Debug($"Unhandled server message: {code}; {message.Length} bytes");
                    break;
                }
            }
            catch (Exception ex)
            {
                Diagnostic.Warning($"Error handling server message: {code}; {ex.Message}", ex);
            }
        }
        /// <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);
            }
        }
Esempio n. 24
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);
            }
        }
Esempio n. 25
0
        /// <summary>
        ///     Gets a new transfer connection using the details in the specified <paramref name="connectToPeerResponse"/>,
        ///     pierces the remote peer's firewall, and retrieves the remote token.
        /// </summary>
        /// <param name="connectToPeerResponse">The response that solicited the connection.</param>
        /// <returns>The operation context, including the new connection and the associated remote token.</returns>
        public async Task <(IConnection Connection, int RemoteToken)> GetTransferConnectionAsync(ConnectToPeerResponse connectToPeerResponse)
        {
            Diagnostic.Debug($"Attempting inbound indirect transfer connection to {connectToPeerResponse.Username} ({connectToPeerResponse.IPEndPoint}) for token {connectToPeerResponse.Token}");

            var connection = ConnectionFactory.GetTransferConnection(
                connectToPeerResponse.IPEndPoint,
                SoulseekClient.Options.TransferConnectionOptions);

            connection.Type          = ConnectionTypes.Inbound | ConnectionTypes.Indirect;
            connection.Disconnected += (sender, e) => Diagnostic.Debug($"Transfer connection to {connectToPeerResponse.Username} ({connectToPeerResponse.IPEndPoint}) for token {connectToPeerResponse.Token} disconnected. (type: {connection.Type}, id: {connection.Id})");

            int remoteToken;

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

                var request = new PierceFirewall(connectToPeerResponse.Token);
                await connection.WriteAsync(request.ToByteArray()).ConfigureAwait(false);

                var remoteTokenBytes = await connection.ReadAsync(4).ConfigureAwait(false);

                remoteToken = BitConverter.ToInt32(remoteTokenBytes, 0);
            }
            catch (Exception ex)
            {
                var msg = $"Failed to establish an inbound indirect transfer connection to {connectToPeerResponse.Username} ({connectToPeerResponse.IPEndPoint}): {ex.Message}";
                Diagnostic.Debug(msg);
                connection.Dispose();

                throw new ConnectionException(msg, ex);
            }

            Diagnostic.Debug($"Transfer connection to {connectToPeerResponse.Username} ({connectToPeerResponse.IPEndPoint}) for token {connectToPeerResponse.Token} established. (type: {connection.Type}, id: {connection.Id})");
            return(connection, remoteToken);
        }