Beispiel #1
0
        /// <summary>
        ///     Handles embedded messages from the server.
        /// </summary>
        /// <param name="message">The message.</param>
        public async void HandleEmbeddedMessage(byte[] message)
        {
            var code = MessageCode.Distributed.Unknown;

            try
            {
                var embeddedMessage = EmbeddedMessage.FromByteArray(message);
                code = embeddedMessage.DistributedCode;
                var distributedMessage = embeddedMessage.DistributedMessage;

                switch (code)
                {
                case MessageCode.Distributed.SearchRequest:
                    // receiving a SearchRequest/3 from the server as an embedded message indicates that we are
                    // operating as a branch root on the distributed network.
                    SoulseekClient.DistributedConnectionManager.PromoteToBranchRoot();

                    var searchRequest = DistributedSearchRequest.FromByteArray(distributedMessage);

                    _ = SoulseekClient.DistributedConnectionManager.BroadcastMessageAsync(message).ConfigureAwait(false);

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

                    break;

                default:
                    Diagnostic.Debug($"Unhandled embedded message: {code}; {message.Length} bytes");
                    break;
                }
            }
            catch (Exception ex)
            {
                Diagnostic.Warning($"Error handling embedded message: {code}; {ex.Message}", ex);
            }
        }
Beispiel #2
0
        public void FromByteArray_Throws_MessageException_On_Code_Mismatch()
        {
            var msg = new MessageBuilder()
                      .WriteCode(MessageCode.Distributed.BranchLevel)
                      .WriteInteger(1)
                      .Build();

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

            Assert.NotNull(ex);
            Assert.IsType <MessageException>(ex);
        }
Beispiel #3
0
        public void FromByteArray_Returns_Expected_Data(string username, int token, string query)
        {
            var msg = new MessageBuilder()
                      .WriteCode(MessageCode.Distributed.SearchRequest)
                      .WriteInteger(0) // unknown
                      .WriteString(username)
                      .WriteInteger(token)
                      .WriteString(query)
                      .Build();

            var response = DistributedSearchRequest.FromByteArray(msg);

            Assert.Equal(username, response.Username);
            Assert.Equal(token, response.Token);
            Assert.Equal(query, response.Query);
        }
Beispiel #4
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 connection = (IMessageConnection)sender;
            var code       = new MessageReader <MessageCode.Distributed>(message).ReadCode();

            if (code != MessageCode.Distributed.SearchRequest && code != MessageCode.Distributed.EmbeddedMessage)
            {
                Diagnostic.Debug($"Distributed message received: {code} from {connection.Username} ({connection.IPEndPoint}) (id: {connection.Id})");
            }
            else if (SoulseekClient.Options.DeduplicateSearchRequests)
            {
                var current = Convert.ToBase64String(message);

                if (DeduplicationHash == current)
                {
                    return;
                }

                DeduplicationHash = current;
            }

            try
            {
                switch (code)
                {
                // if we are connected to a branch root, we will receive EmbeddedMessage/93.
                case MessageCode.Distributed.EmbeddedMessage:
                    var embeddedMessage = EmbeddedMessage.FromByteArray(message);

                    switch (embeddedMessage.DistributedCode)
                    {
                    // convert this message to a normal DistributedSearchRequest before forwarding.  this functionality is based
                    // on the observation that branch roots send embedded messages to children, while parents that are not a branch root
                    // send a plain SearchRequest/3.
                    case MessageCode.Distributed.SearchRequest:
                        var embeddedSearchRequest = DistributedSearchRequest.FromByteArray(embeddedMessage.DistributedMessage);

                        _ = SoulseekClient.DistributedConnectionManager.BroadcastMessageAsync(embeddedMessage.DistributedMessage).ConfigureAwait(false);

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

                        break;

                    default:
                        Diagnostic.Debug($"Unhandled embedded message: {code} from {connection.Username} ({connection.IPEndPoint}); {message.Length} bytes");
                        break;
                    }

                    break;

                // if we are connected to anyone other than a branch root, we will receive SearchRequest/3.
                case MessageCode.Distributed.SearchRequest:
                    var searchRequest = DistributedSearchRequest.FromByteArray(message);

                    _ = SoulseekClient.DistributedConnectionManager.BroadcastMessageAsync(message).ConfigureAwait(false);

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

                    break;

                case MessageCode.Distributed.Ping:
                    var pingResponse = DistributedPingResponse.FromByteArray(message);
                    SoulseekClient.Waiter.Complete(new WaitKey(MessageCode.Distributed.Ping, connection.Username), pingResponse);

                    break;

                case MessageCode.Distributed.BranchLevel:
                    var branchLevel = DistributedBranchLevel.FromByteArray(message);

                    if ((connection.Username, connection.IPEndPoint) == SoulseekClient.DistributedConnectionManager.Parent)
                    {
                        SoulseekClient.DistributedConnectionManager.SetParentBranchLevel(branchLevel.Level);
                    }

                    break;

                case MessageCode.Distributed.BranchRoot:
                    var branchRoot = DistributedBranchRoot.FromByteArray(message);

                    if ((connection.Username, connection.IPEndPoint) == SoulseekClient.DistributedConnectionManager.Parent)
                    {
                        SoulseekClient.DistributedConnectionManager.SetParentBranchRoot(branchRoot.Username);
                    }

                    break;

                case MessageCode.Distributed.ChildDepth:
                    var childDepth = DistributedChildDepth.FromByteArray(message);
                    SoulseekClient.Waiter.Complete(new WaitKey(Constants.WaitKey.ChildDepthMessage, connection.Key), childDepth.Depth);
                    break;

                default:
                    Diagnostic.Debug($"Unhandled distributed message: {code} from {connection.Username} ({connection.IPEndPoint}); {message.Length} bytes");
                    break;
                }
            }
            catch (Exception ex)
            {
                Diagnostic.Warning($"Error handling distributed message: {code} from {connection.Username} ({connection.IPEndPoint}); {ex.Message}", ex);
            }
        }
Beispiel #5
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 connection = (IMessageConnection)sender;
            var code       = new MessageReader <MessageCode.Distributed>(message).ReadCode();

            if (code != MessageCode.Distributed.SearchRequest)
            {
                Diagnostic.Debug($"Distributed message received: {code} from {connection.Username} ({connection.IPAddress}:{connection.Port})");
            }

            try
            {
                switch (code)
                {
                // some clients erroneously send code 93, which is a server code, in place of 3.
                case MessageCode.Distributed.ServerSearchRequest:
                case MessageCode.Distributed.SearchRequest:
                    var            searchRequest = DistributedSearchRequest.FromByteArray(message);
                    SearchResponse searchResponse;

                    SoulseekClient.Waiter.Complete(new WaitKey(Constants.WaitKey.SearchRequestMessage, connection.Context, connection.Key));
                    SoulseekClient.DistributedConnectionManager.BroadcastMessageAsync(message).Forget();

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

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

                        if (searchResponse != null && searchResponse.FileCount > 0)
                        {
                            var(ip, port) = await SoulseekClient.GetUserAddressAsync(searchRequest.Username).ConfigureAwait(false);

                            var peerConnection = await SoulseekClient.PeerConnectionManager.GetOrAddMessageConnectionAsync(searchRequest.Username, ip, port, 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;

                case MessageCode.Distributed.Ping:
                    Diagnostic.Debug($"PING?");
                    var pingResponse = new PingResponse(SoulseekClient.GetNextToken());
                    await connection.WriteAsync(pingResponse.ToByteArray()).ConfigureAwait(false);

                    Diagnostic.Debug($"PONG!");
                    break;

                case MessageCode.Distributed.BranchLevel:
                    var branchLevel = DistributedBranchLevel.FromByteArray(message);

                    SoulseekClient.Waiter.Complete(new WaitKey(Constants.WaitKey.BranchLevelMessage, connection.Context, connection.Key), branchLevel.Level);

                    if ((connection.Username, connection.IPAddress, connection.Port) == SoulseekClient.DistributedConnectionManager.Parent)
                    {
                        SoulseekClient.DistributedConnectionManager.SetBranchLevel(branchLevel.Level);
                    }

                    break;

                case MessageCode.Distributed.BranchRoot:
                    var branchRoot = DistributedBranchRoot.FromByteArray(message);

                    SoulseekClient.Waiter.Complete(new WaitKey(Constants.WaitKey.BranchRootMessage, connection.Context, connection.Key), branchRoot.Username);

                    if ((connection.Username, connection.IPAddress, connection.Port) == SoulseekClient.DistributedConnectionManager.Parent)
                    {
                        SoulseekClient.DistributedConnectionManager.SetBranchRoot(branchRoot.Username);
                    }

                    break;

                case MessageCode.Distributed.ChildDepth:
                    var childDepth = DistributedChildDepth.FromByteArray(message);
                    SoulseekClient.Waiter.Complete(new WaitKey(Constants.WaitKey.ChildDepthMessage, connection.Key), childDepth.Depth);
                    break;

                default:
                    Diagnostic.Debug($"Unhandled distributed message: {code} from {connection.Username} ({connection.IPAddress}:{connection.Port}); {message.Length} bytes");
                    break;
                }
            }
            catch (Exception ex)
            {
                Diagnostic.Warning($"Error handling distributed message: {code} from {connection.Username} ({connection.IPAddress}:{connection.Port}); {ex.Message}", ex);
            }
        }
        /// <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 connection = (IMessageConnection)sender;
            var code       = new MessageReader <MessageCode.Distributed>(message).ReadCode();

            if (code != MessageCode.Distributed.SearchRequest && code != MessageCode.Distributed.ServerSearchRequest)
            {
                Diagnostic.Debug($"Distributed message received: {code} from {connection.Username} ({connection.IPEndPoint}) (id: {connection.Id})");
            }
            else if (SoulseekClient.Options.DeduplicateSearchRequests)
            {
                var current = Convert.ToBase64String(message);

                if (DeduplicationHash == current)
                {
                    return;
                }

                DeduplicationHash = current;
            }

            try
            {
                switch (code)
                {
                // if we are connected to a branch root, we get search requests with code DistributedServerSearchRequest.
                // convert this message to a normal DistributedSearchRequest before forwarding. not sure if this is correct,
                // but it would match the observed behavior. these messages may also be forwarded from the server message
                // handler if we haven't connected to a distributed parent in a timely manner.
                case MessageCode.Distributed.ServerSearchRequest:
                    var serverSearchRequest = DistributedServerSearchRequest.FromByteArray(message);

                    var forwardedSearchRequest = new DistributedSearchRequest(serverSearchRequest.Username, serverSearchRequest.Token, serverSearchRequest.Query);
                    _ = SoulseekClient.DistributedConnectionManager.BroadcastMessageAsync(forwardedSearchRequest.ToByteArray()).ConfigureAwait(false);

                    await TrySendSearchResults(serverSearchRequest.Username, serverSearchRequest.Token, serverSearchRequest.Query).ConfigureAwait(false);

                    break;

                // if we are connected to anyone other than a branch root, we should get search requests with code
                // SearchRequest. forward these requests as is.
                case MessageCode.Distributed.SearchRequest:
                    var searchRequest = DistributedSearchRequest.FromByteArray(message);

                    _ = SoulseekClient.DistributedConnectionManager.BroadcastMessageAsync(searchRequest.ToByteArray()).ConfigureAwait(false);

                    await TrySendSearchResults(searchRequest.Username, searchRequest.Token, searchRequest.Query).ConfigureAwait(false);

                    break;

                case MessageCode.Distributed.Ping:
                    var pingResponse = DistributedPingResponse.FromByteArray(message);
                    SoulseekClient.Waiter.Complete(new WaitKey(MessageCode.Distributed.Ping, connection.Username), pingResponse);

                    break;

                case MessageCode.Distributed.BranchLevel:
                    var branchLevel = DistributedBranchLevel.FromByteArray(message);

                    if ((connection.Username, connection.IPEndPoint) == SoulseekClient.DistributedConnectionManager.Parent)
                    {
                        SoulseekClient.DistributedConnectionManager.SetBranchLevel(branchLevel.Level);
                    }

                    break;

                case MessageCode.Distributed.BranchRoot:
                    var branchRoot = DistributedBranchRoot.FromByteArray(message);

                    if ((connection.Username, connection.IPEndPoint) == SoulseekClient.DistributedConnectionManager.Parent)
                    {
                        SoulseekClient.DistributedConnectionManager.SetBranchRoot(branchRoot.Username);
                    }

                    break;

                case MessageCode.Distributed.ChildDepth:
                    var childDepth = DistributedChildDepth.FromByteArray(message);
                    SoulseekClient.Waiter.Complete(new WaitKey(Constants.WaitKey.ChildDepthMessage, connection.Key), childDepth.Depth);
                    break;

                default:
                    Diagnostic.Debug($"Unhandled distributed message: {code} from {connection.Username} ({connection.IPEndPoint}); {message.Length} bytes");
                    break;
                }
            }
            catch (Exception ex)
            {
                Diagnostic.Warning($"Error handling distributed message: {code} from {connection.Username} ({connection.IPEndPoint}); {ex.Message}", ex);
            }
        }