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
        private static void ControlTime()
        {
            var controls = actions.Where(t => t.Run()).ToList();

            do
            {
                foreach (var action in controls)
                {
                    action.UpdateTime();
                    var date = DateTime.Now;
                    try
                    {
                        action.action();

                        Diagnostic.Debug(LOG, null, $"Performed {action.name} action in {(DateTime.Now - date).TotalSeconds} seconds. next round will be {action.Next}.");
                    }
                    catch (ThreadAbortException ex)
                    {
                        var track = Diagnostic.TrackObject(action);
                        Diagnostic.Error(LOG, track, $"Thread aborted, the {action.name} creates exception.");
                        Diagnostic.Error(LOG, ex);
                    }
                    catch (Exception ex)
                    {
                        var track = Diagnostic.TrackObject(action);
                        Diagnostic.Error(LOG, track, $"Error to execute {action.name} action in the thread.");
                        Diagnostic.Error(LOG, ex);
                    }
                }
                Thread.Sleep(60000); // 1 minute
            } while (true);
        }
Beispiel #3
0
        /// <summary>
        ///     Handles incoming messages from distributed children.
        /// </summary>
        /// <param name="sender">The child <see cref="IMessageConnection"/> from which the message originated.</param>
        /// <param name="message">The message.</param>
        public async void HandleChildMessageRead(object sender, byte[] message)
        {
            var connection = (IMessageConnection)sender;
            var code       = new MessageReader <MessageCode.Distributed>(message).ReadCode();

            Diagnostic.Debug($"Distributed child message received: {code} from {connection.Username} ({connection.IPEndPoint}) (id: {connection.Id})");

            try
            {
                switch (code)
                {
                case MessageCode.Distributed.ChildDepth:
                    break;

                case MessageCode.Distributed.Ping:
                    var pingResponse = new DistributedPingResponse(SoulseekClient.GetNextToken());
                    await connection.WriteAsync(pingResponse).ConfigureAwait(false);

                    break;

                default:
                    Diagnostic.Debug($"Unhandled distributed child message: {code} from {connection.Username} ({connection.IPEndPoint}); {message.Length} bytes");
                    break;
                }
            }
            catch (Exception ex)
            {
                Diagnostic.Warning($"Error handling distributed child message: {code} from {connection.Username} ({connection.IPEndPoint}); {ex.Message}", ex);
            }
        }
Beispiel #4
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);
        }
        /// <summary>
        ///     Handles outgoing messages, post send.
        /// </summary>
        /// <param name="sender">The <see cref="IMessageConnection"/> instance to which the message was sent.</param>
        /// <param name="args">The message event args.</param>
        public void HandleMessageWritten(object sender, MessageEventArgs args)
        {
            var connection = (IMessageConnection)sender;
            var code       = new MessageReader <MessageCode.Peer>(args.Message).ReadCode();

            Diagnostic.Debug($"Peer message sent: {code} ({connection.IPEndPoint}) (id: {connection.Id})");
        }
        private async Task <IMessageConnection> GetMessageConnectionOutboundDirectAsync(string username, IPEndPoint ipEndPoint, CancellationToken cancellationToken)
        {
            Diagnostic.Debug($"Attempting direct message connection to {username} ({ipEndPoint})");

            var connection = ConnectionFactory.GetMessageConnection(
                username,
                ipEndPoint,
                SoulseekClient.Options.PeerConnectionOptions);

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

            try
            {
                await connection.ConnectAsync(cancellationToken).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                Diagnostic.Debug($"Failed to establish a direct message connection to {username} ({ipEndPoint}): {ex.Message}");
                connection.Dispose();
                throw;
            }

            Diagnostic.Debug($"Direct message connection to {username} ({ipEndPoint}) established. (type: {connection.Type}, id: {connection.Id})");
            return(connection);
        }
Beispiel #7
0
        /// <summary>
        ///     Handles outgoing messages to distributed children, post send.
        /// </summary>
        /// <param name="sender">The child <see cref="IMessageConnection"/> instance to which the message was sent.</param>
        /// <param name="args">The message event args.</param>
        public void HandleChildMessageWritten(object sender, MessageEventArgs args)
        {
            var connection = (IMessageConnection)sender;
            var code       = new MessageReader <MessageCode.Distributed>(args.Message).ReadCode();

            Diagnostic.Debug($"Distributed child message sent: {code} to {connection.Username} ({connection.IPEndPoint}) (id: {connection.Id})");
        }
Beispiel #8
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);
            }
        }
        /// <summary>
        ///     Adds a new transfer connection from an incoming connection.
        /// </summary>
        /// <param name="username">The username of the user from which the connection originated.</param>
        /// <param name="token">The token with which the firewall was pierced.</param>
        /// <param name="incomingConnection">The accepted connection.</param>
        /// <returns>The operation context.</returns>
        public async Task <(IConnection Connection, int RemoteToken)> AddTransferConnectionAsync(string username, int token, IConnection incomingConnection)
        {
            Diagnostic.Debug($"Inbound transfer connection to {username} ({incomingConnection.IPEndPoint}) for token {token} accepted. (type: {incomingConnection.Type}, id: {incomingConnection.Id}");

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

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

            Diagnostic.Debug($"Inbound transfer connection to {username} ({connection.IPEndPoint}) for token {token} handed off. (old: {incomingConnection.Id}, new: {connection.Id})");

            int remoteToken;

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

                remoteToken = BitConverter.ToInt32(remoteTokenBytes, 0);
            }
            catch (Exception ex)
            {
                var msg = $"Failed to establish an inbound transfer connection to {username} ({incomingConnection.IPEndPoint}) for token {token}: {ex.Message}";
                Diagnostic.Debug($"{msg} (type: {connection.Type}, id: {connection.Id})");
                connection.Dispose();
                throw new ConnectionException(msg, ex);
            }

            Diagnostic.Debug($"Transfer connection to {username} ({connection.IPEndPoint}) for token {remoteToken} established. (type: {connection.Type}, id: {connection.Id})");
            return(connection, remoteToken);
        }
Beispiel #10
0
        /// <summary>
        ///     Gets a new transfer connection to the specified <paramref name="username"/> using the specified <paramref name="token"/>.
        /// </summary>
        /// <remarks>A direct connection is attempted first, and, if unsuccessful, an indirect connection is attempted.</remarks>
        /// <param name="username">The username of the user to which to connect.</param>
        /// <param name="ipEndPoint">The remote IP endpoint of the connection.</param>
        /// <param name="token">The token with which to initialize the connection.</param>
        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
        /// <returns>The operation context, including the new connection.</returns>
        public async Task <IConnection> GetTransferConnectionAsync(string username, IPEndPoint ipEndPoint, int token, CancellationToken cancellationToken)
        {
            using (var directCts = new CancellationTokenSource())
                using (var directLinkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, directCts.Token))
                    using (var indirectCts = new CancellationTokenSource())
                        using (var indirectLinkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, indirectCts.Token))
                        {
                            Diagnostic.Debug($"Attempting simultaneous direct and indirect transfer connections to {username} ({ipEndPoint})");

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

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

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

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

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

                            var connection = await task.ConfigureAwait(false);

                            var isDirect = task == direct;

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

                            try
                            {
                                if (isDirect)
                                {
                                    var request = new PeerInit(SoulseekClient.Username, Constants.ConnectionType.Transfer, token).ToByteArray();
                                    await connection.WriteAsync(request, cancellationToken).ConfigureAwait(false);
                                }

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

                            Diagnostic.Debug($"Transfer connection to {username} ({ipEndPoint}) established. (type: {connection.Type}, id: {connection.Id})");
                            return(connection);
                        }
        }
Beispiel #11
0
        public static void Delete(FileInfo file)
        {
            System.IO.File.Delete(file.FullName);

            if (R.DebugMode)
            {
                Diagnostic.Debug(LOG, null, "File deleted: {0}", file.FullName);
            }
        }
        private void TryRemoveMessageConnectionRecord(IMessageConnection connection)
        {
            if (MessageConnectionDictionary.TryRemove(connection.Username, out _))
            {
                Diagnostic.Debug($"Removed message connection record for {connection.Key.Username} ({connection.IPEndPoint}) (type: {connection.Type}, id: {connection.Id})");
            }

            Diagnostic.Debug($"Message connection cache now contains {MessageConnectionDictionary.Count} connections.");
        }
        private void MessageConnection_Disconnected(object sender, ConnectionDisconnectedEventArgs e)
        {
            var connection = (IMessageConnection)sender;

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

            TryRemoveMessageConnectionRecord(connection);
            connection.Dispose();
        }
Beispiel #14
0
        public static void Delete(FileInfo[] files)
        {
            foreach (var file in files)
            {
                System.IO.File.Delete(file.FullName);
            }

            if (R.DebugMode && files.Length > 0)
            {
                var track = Diagnostic.TrackMessages(Array.ConvertAll(files, t => t.FullName));
                Diagnostic.Debug(LOG, null, "Deleted {0} files", files.Length);
            }
        }
Beispiel #15
0
        private async Task <bool> TrySendSearchResults(string username, int token, string query)
        {
            if (SoulseekClient.Options.SearchResponseResolver == default)
            {
                return(false);
            }

            SearchResponse searchResponse = null;

            try
            {
                searchResponse = await SoulseekClient.Options.SearchResponseResolver(username, token, SearchQuery.FromText(query)).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                Diagnostic.Warning($"Error resolving search response for query '{query}' requested by {username} with token {token}: {ex.Message}", ex);
                return(false);
            }

            if (searchResponse == null)
            {
                return(false);
            }

            if (searchResponse.FileCount <= 0)
            {
                return(false);
            }

            try
            {
                Diagnostic.Debug($"Resolved {searchResponse.FileCount} files for query '{query}' with token {token} from {username}");

                var endpoint = await SoulseekClient.GetUserEndPointAsync(username).ConfigureAwait(false);

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

                await peerConnection.WriteAsync(searchResponse.ToByteArray()).ConfigureAwait(false);

                Diagnostic.Debug($"Sent response containing {searchResponse.FileCount} files to {username} for query '{query}' with token {token}");

                return(true);
            }
            catch (Exception ex)
            {
                Diagnostic.Debug($"Failed to send search response for {query} to {username}: {ex.Message}", ex);
            }

            return(false);
        }
Beispiel #16
0
        /// <summary>
        ///     Adds a new message connection from an incoming connection.
        /// </summary>
        /// <remarks>
        ///     This method will be invoked from <see cref="ListenerHandler"/> upon receipt of an incoming unsolicited message
        ///     only. Because this connection is fully established by the time it is passed to this method, it must supercede any
        ///     cached connection, as it will be the most recently established connection as tracked by the remote user.
        /// </remarks>
        /// <param name="username">The username of the user from which the connection originated.</param>
        /// <param name="incomingConnection">The the accepted connection.</param>
        /// <returns>The operation context.</returns>
        public async Task AddMessageConnectionAsync(string username, IConnection incomingConnection)
        {
            var c = incomingConnection;

            try
            {
                await MessageConnectionDictionary.AddOrUpdate(
                    username,
                    new Lazy <Task <IMessageConnection> >(() => GetConnection()),
                    (key, cachedConnectionRecord) => new Lazy <Task <IMessageConnection> >(() => GetConnection(cachedConnectionRecord))).Value.ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                var msg = $"Failed to establish an inbound message connection to {username} ({c.IPEndPoint}): {ex.Message}";
                Diagnostic.Debug($"{msg} (type: {c.Type}, id: {c.Id})");
                Diagnostic.Debug($"Purging message connection cache of failed connection to {username} ({c.IPEndPoint}).");
                MessageConnectionDictionary.TryRemove(username, out _);
                throw new ConnectionException(msg, ex);
            }

            async Task <IMessageConnection> GetConnection(Lazy <Task <IMessageConnection> > cachedConnectionRecord = null)
            {
                Diagnostic.Debug($"Inbound message connection to {username} ({c.IPEndPoint}) accepted. (type: {c.Type}, id: {c.Id})");

                var connection = ConnectionFactory.GetMessageConnection(
                    username,
                    c.IPEndPoint,
                    SoulseekClient.Options.PeerConnectionOptions,
                    c.HandoffTcpClient());

                connection.Type          = ConnectionTypes.Inbound | ConnectionTypes.Direct;
                connection.MessageRead  += SoulseekClient.PeerMessageHandler.HandleMessageRead;
                connection.Disconnected += MessageConnection_Disconnected;

                Diagnostic.Debug($"Inbound message connection to {username} ({connection.IPEndPoint}) handed off. (old: {c.Id}, new: {connection.Id})");

                if (cachedConnectionRecord != null)
                {
                    var cachedConnection = await cachedConnectionRecord.Value.ConfigureAwait(false);

                    cachedConnection.Disconnected -= MessageConnection_Disconnected;
                    Diagnostic.Debug($"Superceding cached message connection to {username} ({cachedConnection.IPEndPoint}) (old: {cachedConnection.Id}, new: {connection.Id}");
                }

                connection.StartReadingContinuously();

                Diagnostic.Debug($"Message connection to {username} ({connection.IPEndPoint}) established. (type: {connection.Type}, id: {connection.Id})");
                return(connection);
            }
        }
Beispiel #17
0
 public string GetPassword()
 {
     try
     {
         return(k.Security.Decrypt(EPassword, User));
     }catch (Exception ex)
     {
         var track = Diagnostic.TrackObject(this);
         Diagnostic.Debug(this, track, $"The invalid epassword, may be you added password before add user");
         Diagnostic.Error(LOG, ex);
         throw new KException(LOG, E.Message.CredPasswordError_0);
         throw ex;
     }
 }
Beispiel #18
0
        /// <summary>
        ///     Awaits an incoming transfer connection from the specified <paramref name="username"/> for the specified
        ///     <paramref name="filename"/> and <paramref name="remoteToken"/>.
        /// </summary>
        /// <remarks>
        ///     After this method is invoked, a <see cref="TransferResponse"/> message with the <paramref name="remoteToken"/>
        ///     must be sent to the <paramref name="username"/> via a message connection to signal the remote peer to initate the connection.
        /// </remarks>
        /// <param name="username">The username of the user from which the connection is expected.</param>
        /// <param name="filename">The filename associated with the expected transfer.</param>
        /// <param name="remoteToken">The remote token associated with the expected transfer.</param>
        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
        /// <returns>The operation context, including the established connection.</returns>
        public async Task <IConnection> AwaitTransferConnectionAsync(string username, string filename, int remoteToken, CancellationToken cancellationToken)
        {
            using (var directCts = new CancellationTokenSource())
                using (var directLinkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, directCts.Token))
                    using (var indirectCts = new CancellationTokenSource())
                        using (var indirectLinkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, indirectCts.Token))
                        {
                            Diagnostic.Debug($"Waiting for a direct or indirect connection from {username} with remote token {remoteToken} for {filename}");

                            // completed in ServerMessageHandler upon receipt of a ConnectToPeerResponse.
                            var indirect = SoulseekClient.Waiter.Wait <IConnection>(
                                key: new WaitKey(Constants.WaitKey.IndirectTransfer, username, filename, remoteToken),
                                timeout: SoulseekClient.Options.TransferConnectionOptions.ConnectTimeout,
                                cancellationToken: indirectLinkedCts.Token);

                            // completed in AddTransferConnectionAsync when handling the incoming connection within ListenerHandler.
                            var direct = SoulseekClient.Waiter.Wait <IConnection>(
                                key: new WaitKey(Constants.WaitKey.DirectTransfer, username, remoteToken),
                                timeout: SoulseekClient.Options.TransferConnectionOptions.ConnectTimeout,
                                cancellationToken: directLinkedCts.Token);

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

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

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

                            if (task.Status != TaskStatus.RanToCompletion)
                            {
                                var msg = $"Failed to establish a direct or indirect transfer connection to {username} with remote token {remoteToken} for {filename}";
                                Diagnostic.Debug(msg);
                                throw new ConnectionException(msg);
                            }

                            var connection = await task.ConfigureAwait(false);

                            var isDirect = task == direct;

                            Diagnostic.Debug($"{(isDirect ? "Direct" : "Indirect")} transfer connection to {username} ({connection.IPEndPoint}) with remote token {remoteToken} for {filename} established first, attempting to cancel {(isDirect ? "indirect" : "direct")} connection.");
                            (isDirect ? indirectCts : directCts).Cancel();

                            Diagnostic.Debug($"Transfer connection to {username} ({connection.IPEndPoint}) with remote token {remoteToken} for {filename} established. (type: {connection.Type}, id: {connection.Id})");
                            return(connection);
                        }
        }
Beispiel #19
0
        private async Task <IMessageConnection> GetMessageConnectionOutboundIndirectAsync(string username, CancellationToken cancellationToken)
        {
            var solicitationToken = SoulseekClient.GetNextToken();

            Diagnostic.Debug($"Soliciting indirect message connection to {username} with token {solicitationToken}");

            try
            {
                PendingSolicitationDictionary.TryAdd(solicitationToken, username);

                await SoulseekClient.ServerConnection
                .WriteAsync(new ConnectToPeerRequest(solicitationToken, username, Constants.ConnectionType.Peer), cancellationToken)
                .ConfigureAwait(false);

                using var incomingConnection = await SoulseekClient.Waiter
                                               .Wait <IConnection>(new WaitKey (Constants.WaitKey.SolicitedPeerConnection, username, solicitationToken), SoulseekClient.Options.PeerConnectionOptions.ConnectTimeout, cancellationToken)
                                               .ConfigureAwait(false);

                var connection = ConnectionFactory.GetMessageConnection(
                    username,
                    incomingConnection.IPEndPoint,
                    SoulseekClient.Options.PeerConnectionOptions,
                    incomingConnection.HandoffTcpClient());

                Diagnostic.Debug($"Indirect message connection to {username} ({incomingConnection.IPEndPoint}) handed off. (old: {incomingConnection.Id}, new: {connection.Id})");

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

                Diagnostic.Debug($"Indirect message connection to {username} ({connection.IPEndPoint}) established. (type: {connection.Type}, id: {connection.Id})");
                return(connection);
            }
            catch (Exception ex)
            {
                Diagnostic.Debug($"Failed to establish an indirect message connection to {username} with token {solicitationToken}: {ex.Message}");
                throw;
            }
            finally
            {
                PendingSolicitationDictionary.TryRemove(solicitationToken, out _);
            }
        }
Beispiel #20
0
        internal static void End()
        {
            try
            {
                if (thread != null && thread.IsAlive)
                {
                    thread.Abort();
                }

                Diagnostic.Debug(LOG, null, $"Control thread finished");
            }
            catch (Exception ex)
            {
                var track = Diagnostic.TrackObject(actions);
                Diagnostic.Error(LOG, track, $"Fatal control thread error trying to terminate thread.");
                Diagnostic.Error(LOG, ex);
            }
        }
        /// <summary>
        ///     Gets an existing message connection to the specified <paramref name="username"/>, if one exists.
        /// </summary>
        /// <param name="username">The username of the user for which to retrieve the cached connection.</param>
        /// <returns>The operation context, including the cached connection, or null if one does not exist.</returns>
        public async Task <IMessageConnection> GetCachedMessageConnectionAsync(string username)
        {
            try
            {
                if (MessageConnectionDictionary.TryGetValue(username, out var cached))
                {
                    var connection = await cached.Value.ConfigureAwait(false);

                    Diagnostic.Debug($"Retrieved cached message connection to {connection.Username} ({connection.IPEndPoint}) (type: {connection.Type}, id: {connection.Id})");
                    return(connection);
                }
            }
            catch (Exception ex)
            {
                Diagnostic.Debug($"Failed to retrieve cached message connection to {username}: {ex.Message}");
            }

            return(null);
        }
Beispiel #22
0
        private async Task <IConnection> GetTransferConnectionOutboundIndirectAsync(string username, int token, CancellationToken cancellationToken)
        {
            Diagnostic.Debug($"Soliciting indirect transfer connection to {username} with token {token}");

            var solicitationToken = SoulseekClient.GetNextToken();

            try
            {
                PendingSolicitationDictionary.TryAdd(solicitationToken, username);

                await SoulseekClient.ServerConnection
                .WriteAsync(new ConnectToPeerRequest(solicitationToken, username, Constants.ConnectionType.Transfer).ToByteArray(), cancellationToken)
                .ConfigureAwait(false);

                using (var incomingConnection = await SoulseekClient.Waiter
                                                .Wait <IConnection>(new WaitKey(Constants.WaitKey.SolicitedPeerConnection, username, solicitationToken), SoulseekClient.Options.TransferConnectionOptions.ConnectTimeout, cancellationToken)
                                                .ConfigureAwait(false))
                {
                    var connection = ConnectionFactory.GetTransferConnection(
                        incomingConnection.IPEndPoint,
                        SoulseekClient.Options.TransferConnectionOptions,
                        incomingConnection.HandoffTcpClient());

                    Diagnostic.Debug($"Indirect transfer connection to {username} ({incomingConnection.IPEndPoint}) handed off. (old: {incomingConnection.Id}, new: {connection.Id})");

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

                    Diagnostic.Debug($"Indirect transfer connection for {token} ({connection.IPEndPoint}) established. (type: {connection.Type}, id: {connection.Id})");
                    return(connection);
                }
            }
            catch (Exception ex)
            {
                Diagnostic.Debug($"Failed to establish an indirect transfer connection to {username} with token {token}: {ex.Message}");
                throw;
            }
            finally
            {
                PendingSolicitationDictionary.TryRemove(solicitationToken, out var _);
            }
        }
Beispiel #23
0
        internal static void Start()
        {
            try
            {
                if (actions == null)
                {
                    actions = new List <ThreadStruct>();
                }

                thread = new Thread(new ThreadStart(ControlTime));
                thread.Start();
                Diagnostic.Debug(LOG, null, $"Control thread started");
            }
            catch (Exception ex)
            {
                var track = Diagnostic.TrackObject(actions);
                Diagnostic.Error(LOG, track, $"Fatal control thread error.");
                Diagnostic.Error(LOG, ex);
            }
        }
        /// <summary>
        ///     Sends the pending response matching the specified <paramref name="responseToken"/>, if one exists.
        /// </summary>
        /// <remarks>
        ///     This overload is called by the listener when an incoming connection is established with a pierce firewall token,
        ///     and if that token doesn't match a pending solicitation, and if the token matches a cached search response.  In this case,
        ///     the connection is retrieved from the cache and used to send the response.
        /// </remarks>
        /// <param name="responseToken">The token matching the pending response to send.</param>
        /// <returns>The operation context, including a value indicating whether a response was successfully sent.</returns>
        public async Task <bool> TryRespondAsync(int responseToken)
        {
            if (SoulseekClient.Options.SearchResponseCache != default)
            {
                bool cached;
                (string Username, int Token, string Query, SearchResponse SearchResponse)record;

                try
                {
                    cached = SoulseekClient.Options.SearchResponseCache.TryRemove(responseToken, out record);
                }
                catch (Exception ex)
                {
                    Diagnostic.Warning($"Error retrieving cached search response {responseToken}: {ex.Message}", ex);
                    return(false);
                }

                if (cached)
                {
                    var(username, token, query, searchResponse) = record;

                    try
                    {
                        var peerConnection = await SoulseekClient.PeerConnectionManager.GetCachedMessageConnectionAsync(username).ConfigureAwait(false);

                        await peerConnection.WriteAsync(searchResponse.ToByteArray()).ConfigureAwait(false);

                        Diagnostic.Debug($"Sent cached response {responseToken} containing {searchResponse.FileCount + searchResponse.LockedFileCount} files to {username} for query '{query}' with token {token}");
                        ResponseDelivered?.Invoke(this, new SearchRequestResponseEventArgs(username, token, query, searchResponse));
                        return(true);
                    }
                    catch (Exception ex)
                    {
                        Diagnostic.Debug($"Failed to send cached search response {responseToken} to {username} for query '{query}' with token {token}: {ex.Message}", ex);
                        ResponseDeliveryFailed?.Invoke(this, new SearchRequestResponseEventArgs(username, token, query, searchResponse));
                    }
                }
            }

            return(false);
        }
        public async void HandleMessage(object sender, byte[] message)
        {
            var connection = (IMessageConnection)sender;
            var code       = new MessageReader <MessageCode.Distributed>(message).ReadCode();

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

            try
            {
                switch (code)
                {
                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);
            }
        }
Beispiel #26
0
        public static void ZipFiles(string[] files, string destination)
        {
            using (var zip = new ZipFile())
            {
                foreach (var file in files)
                {
                    if (System.IO.File.Exists(file))
                    {
                        zip.AddFile(file);
                    }

                    zip.Save(destination);
                }

                if (R.DebugMode && files.Length > 0)
                {
                    var track = Diagnostic.TrackMessages(files);
                    Diagnostic.Debug(typeof(File).Name, track, "Compacted {0} files to {1}", files.Length, destination);
                }
            }
        }
        private async Task <IConnection> GetTransferConnectionOutboundDirectAsync(IPEndPoint ipEndPoint, int token, CancellationToken cancellationToken)
        {
            Diagnostic.Debug($"Attempting direct transfer connection for token {token} to {ipEndPoint}");

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

            connection.Type          = ConnectionTypes.Outbound | ConnectionTypes.Direct;
            connection.Disconnected += (sender, e) => Diagnostic.Debug($"Transfer connection for token {token} to {ipEndPoint} disconnected. (type: {connection.Type}, id: {connection.Id})");

            try
            {
                await connection.ConnectAsync(cancellationToken).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                Diagnostic.Debug($"Failed to establish a direct transfer connection for token {token} to ({ipEndPoint}): {ex.Message}");
                connection.Dispose();
                throw;
            }

            Diagnostic.Debug($"Direct transfer connection for {token} to {connection.IPEndPoint} established. (type: {connection.Type}, id: {connection.Id})");
            return(connection);
        }
Beispiel #28
0
        public static void Add(Action staticMethod, string description, int minutes)
        {
            if (actions == null)
            {
                actions = new List <ThreadStruct>();
            }

            var name   = staticMethod.Method.DeclaringType.FullName;
            var thread = new ThreadStruct(staticMethod, description, minutes);
            var track  = Diagnostic.TrackObject(thread);

            if (!actions.Where(t => t.name == name).Any())
            {
                actions.Add(thread);

                Diagnostic.Debug(LOG, track, $"Added {thread.name} action in the thread. Details: {thread.description}.");
            }
            else
            {
                var index = actions.FindIndex(t => t.name == name);
                actions[index] = thread;
                Diagnostic.Debug(LOG, track, $"Updated {thread.name} action in the thread. Details: {thread.description}.");
            }
        }
        /// <summary>
        ///     Discards the cached response matching the specified <paramref name="responseToken"/>, if one exists.
        /// </summary>
        /// <param name="responseToken">The token matching the cached response to discard.</param>
        /// <returns>A value indicating whether the cached response was discarded.</returns>
        public bool TryDiscard(int responseToken)
        {
            if (SoulseekClient.Options.SearchResponseCache != default)
            {
                try
                {
                    if (SoulseekClient.Options.SearchResponseCache.TryRemove(responseToken, out var response))
                    {
                        var(username, token, query, searchResponse) = response;

                        Diagnostic.Debug($"Discarded cached search response {responseToken} to {username} for query '{query}' with token {token}");
                        ResponseDeliveryFailed?.Invoke(this, new SearchRequestResponseEventArgs(username, token, query, searchResponse));
                        return(true);
                    }
                }
                catch (Exception ex)
                {
                    Diagnostic.Warning($"Error removing cached search response {responseToken}: {ex.Message}", ex);
                    return(false);
                }
            }

            return(false);
        }
Beispiel #30
0
        /// <summary>
        ///     Handles outgoing messages, post send.
        /// </summary>
        /// <param name="sender">The <see cref="IMessageConnection"/> instance to which the message was sent.</param>
        /// <param name="args">The message event args.</param>
        public void HandleMessageWritten(object sender, MessageEventArgs args)
        {
            var code = new MessageReader <MessageCode.Distributed>(args.Message).ReadCode();

            Diagnostic.Debug($"Distributed message sent: {code}");
        }