/// <summary> /// Creates a new instance of <see cref="PeerInit"/> from the specified <paramref name="bytes"/>. /// </summary> /// <param name="bytes">The byte array from which to parse.</param> /// <param name="response">The parsed instance.</param> /// <returns>A value indicating whether the message was successfully parsed.</returns> public static bool TryFromByteArray(byte[] bytes, out PeerInit response) { response = null; try { var reader = new MessageReader <MessageCode.Initialization>(bytes); if (reader.ReadCode() != MessageCode.Initialization.PeerInit) { return(false); } var username = reader.ReadString(); var transferType = reader.ReadString(); var token = reader.ReadInteger(); response = new PeerInit(username, transferType, token); return(true); } catch { return(false); } }
public void Instantiates_With_The_Given_Data(string username, string transferType, int token) { var r = new PeerInit(username, transferType, token); Assert.Equal(username, r.Username); Assert.Equal(transferType, r.ConnectionType); Assert.Equal(token, r.Token); }
/// <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); } }
public void TryParse_Returns_False_On_Code_Mismatch() { var msg = new List <byte>(); msg.AddRange(BitConverter.GetBytes(0)); // overall length, ignored for this test. msg.Add((byte)MessageCode.Initialization.PierceFirewall); var r = PeerInit.TryFromByteArray(msg.ToArray(), out var result); Assert.False(r); Assert.Null(result); }
public void Adds_Distributed_Connection_On_Distributed_PeerInit(IPEndPoint endpoint, string username, int token) { var(handler, mocks) = GetFixture(endpoint); var message = new PeerInit(username, Constants.ConnectionType.Distributed, token); var messageBytes = message.ToByteArray().AsSpan().Slice(4).ToArray(); mocks.Connection.Setup(m => m.ReadAsync(4, It.IsAny <CancellationToken?>())) .Returns(Task.FromResult(BitConverter.GetBytes(messageBytes.Length))); mocks.Connection.Setup(m => m.ReadAsync(messageBytes.Length, It.IsAny <CancellationToken?>())) .Returns(Task.FromResult(messageBytes)); handler.HandleConnection(null, mocks.Connection.Object); mocks.DistributedConnectionManager.Verify(m => m.AddChildConnectionAsync(username, It.IsAny <IConnection>())); }
public void Adds_Transfer_Connection_On_Transfer_PeerInit(IPAddress ip, string username, int token) { var(handler, mocks) = GetFixture(ip); var message = new PeerInit(username, Constants.ConnectionType.Transfer, token); var messageBytes = message.ToByteArray().AsSpan().Slice(4).ToArray(); mocks.Connection.Setup(m => m.ReadAsync(4, It.IsAny <CancellationToken?>())) .Returns(Task.FromResult(BitConverter.GetBytes(messageBytes.Length))); mocks.Connection.Setup(m => m.ReadAsync(messageBytes.Length, It.IsAny <CancellationToken?>())) .Returns(Task.FromResult(messageBytes)); handler.HandleConnection(null, mocks.Connection.Object); mocks.PeerConnectionManager.Verify(m => m.AddTransferConnectionAsync(username, token, It.IsAny <ITcpClient>())); }
public void PeerInitRequest_Constructs_The_Correct_Message() { var name = Guid.NewGuid().ToString(); var token = new Random().Next(); var a = new PeerInit(name, "P", token); var msg = a.ToByteArray(); var reader = new MessageReader <MessageCode.Initialization>(msg); var code = reader.ReadCode(); Assert.Equal(MessageCode.Initialization.PeerInit, code); Assert.Equal(4 + 1 + 4 + name.Length + "P".Length + 8, msg.Length); Assert.Equal(name, reader.ReadString()); Assert.Equal("P", reader.ReadString()); Assert.Equal(token, reader.ReadInteger()); }
public void TryParse_Returns_False_On_Missing_Data(string username, char type) { var msg = new List <byte>(); msg.AddRange(BitConverter.GetBytes(0)); // overall length, ignored for this test. msg.Add((byte)MessageCode.Initialization.PeerInit); msg.AddRange(BitConverter.GetBytes(3)); // name len msg.AddRange(Encoding.ASCII.GetBytes(username)); // name msg.AddRange(BitConverter.GetBytes(1)); // type len msg.AddRange(Encoding.ASCII.GetBytes(type.ToString(CultureInfo.InvariantCulture))); // type // omit token var r = PeerInit.TryFromByteArray(msg.ToArray(), out var result); Assert.False(r); Assert.Null(result); }
public void Creates_Diagnostic_On_PeerInit(IPEndPoint endpoint, string username, int token) { var(handler, mocks) = GetFixture(endpoint); mocks.Diagnostic.Setup(m => m.Debug(It.IsAny <string>())); var message = new PeerInit(username, Constants.ConnectionType.Peer, token); var messageBytes = message.ToByteArray().AsSpan().Slice(4).ToArray(); mocks.Connection.Setup(m => m.ReadAsync(4, It.IsAny <CancellationToken?>())) .Returns(Task.FromResult(BitConverter.GetBytes(messageBytes.Length))); mocks.Connection.Setup(m => m.ReadAsync(messageBytes.Length, It.IsAny <CancellationToken?>())) .Returns(Task.FromResult(messageBytes)); handler.HandleConnection(null, mocks.Connection.Object); var compare = StringComparison.InvariantCultureIgnoreCase; mocks.Diagnostic.Verify(m => m.Debug(It.Is <string>(s => s.Contains("PeerInit for connection type", compare))), Times.Once); }
public void TryParse_Returns_Expected_Data(string username, char type, int token) { var msg = new List <byte>(); msg.AddRange(BitConverter.GetBytes(0)); // overall length, ignored for this test. msg.Add((byte)MessageCode.Initialization.PeerInit); msg.AddRange(BitConverter.GetBytes(username.Length)); // name len msg.AddRange(Encoding.ASCII.GetBytes(username)); // name msg.AddRange(BitConverter.GetBytes(1)); // type len msg.AddRange(Encoding.ASCII.GetBytes(type.ToString(CultureInfo.InvariantCulture))); // type msg.AddRange(BitConverter.GetBytes(token)); // omit token var r = PeerInit.TryFromByteArray(msg.ToArray(), out var result); Assert.True(r); Assert.NotNull(result); Assert.Equal(username, result.Username); Assert.Equal(type.ToString(CultureInfo.InvariantCulture), result.ConnectionType); Assert.Equal(token, result.Token); }
public void Completes_DirectTransfer_Wait_On_Transfer_PeerInit(IPEndPoint endpoint, string username, int token) { var(handler, mocks) = GetFixture(endpoint); var message = new PeerInit(username, Constants.ConnectionType.Transfer, token); var messageBytes = message.ToByteArray().AsSpan().Slice(4).ToArray(); mocks.Connection.Setup(m => m.ReadAsync(4, It.IsAny <CancellationToken?>())) .Returns(Task.FromResult(BitConverter.GetBytes(messageBytes.Length))); mocks.Connection.Setup(m => m.ReadAsync(messageBytes.Length, It.IsAny <CancellationToken?>())) .Returns(Task.FromResult(messageBytes)); var newTransfer = new Mock <IConnection>(); mocks.PeerConnectionManager.Setup(m => m.AddTransferConnectionAsync(username, token, It.IsAny <IConnection>())) .Returns(Task.FromResult((newTransfer.Object, token))); handler.HandleConnection(null, mocks.Connection.Object); var key = new WaitKey(Constants.WaitKey.DirectTransfer, username, token); mocks.Waiter.Verify(m => m.Complete(key, newTransfer.Object), Times.Once); }
/// <summary> /// Handle <see cref="IListener.Accepted"/> events. /// </summary> /// <param name="sender">The originating <see cref="IListener"/> instance.</param> /// <param name="connection">The accepted connection.</param> public async void HandleConnection(object sender, IConnection connection) { Diagnostic.Debug($"Accepted incoming connection from {connection.IPEndPoint.Address}:{SoulseekClient.Listener.Port} (id: {connection.Id})"); try { var lengthBytes = await connection.ReadAsync(4).ConfigureAwait(false); var length = BitConverter.ToInt32(lengthBytes, 0); var bodyBytes = await connection.ReadAsync(length).ConfigureAwait(false); byte[] message = lengthBytes.Concat(bodyBytes).ToArray(); if (PeerInit.TryFromByteArray(message, out var peerInit)) { // this connection is the result of an unsolicited connection from the remote peer, either to request info or // browse, or to send a file. Diagnostic.Debug($"PeerInit for connection type {peerInit.ConnectionType} received from {peerInit.Username} ({connection.IPEndPoint.Address}:{SoulseekClient.Listener.Port}) (id: {connection.Id})"); if (peerInit.ConnectionType == Constants.ConnectionType.Peer) { await SoulseekClient.PeerConnectionManager.AddMessageConnectionAsync( peerInit.Username, connection).ConfigureAwait(false); } else if (peerInit.ConnectionType == Constants.ConnectionType.Transfer) { var(transferConnection, remoteToken) = await SoulseekClient.PeerConnectionManager.AddTransferConnectionAsync( peerInit.Username, peerInit.Token, connection).ConfigureAwait(false); SoulseekClient.Waiter.Complete(new WaitKey(Constants.WaitKey.DirectTransfer, peerInit.Username, remoteToken), transferConnection); } else if (peerInit.ConnectionType == Constants.ConnectionType.Distributed) { await SoulseekClient.DistributedConnectionManager.AddChildConnectionAsync( peerInit.Username, connection).ConfigureAwait(false); } } else if (PierceFirewall.TryFromByteArray(message, out var pierceFirewall)) { // this connection is the result of a ConnectToPeer request sent to the user, and the incoming message will // contain the token that was provided in the request. Ensure this token is among those expected, and use it // to determine the username of the remote user. if (SoulseekClient.PeerConnectionManager.PendingSolicitations.TryGetValue(pierceFirewall.Token, out var peerUsername)) { Diagnostic.Debug($"Peer PierceFirewall with token {pierceFirewall.Token} received from {peerUsername} ({connection.IPEndPoint.Address}:{SoulseekClient.Listener.Port}) (id: {connection.Id})"); SoulseekClient.Waiter.Complete(new WaitKey(Constants.WaitKey.SolicitedPeerConnection, peerUsername, pierceFirewall.Token), connection); } else if (SoulseekClient.DistributedConnectionManager.PendingSolicitations.TryGetValue(pierceFirewall.Token, out var distributedUsername)) { Diagnostic.Debug($"Distributed PierceFirewall with token {pierceFirewall.Token} received from {distributedUsername} ({connection.IPEndPoint.Address}:{SoulseekClient.Listener.Port}) (id: {connection.Id})"); SoulseekClient.Waiter.Complete(new WaitKey(Constants.WaitKey.SolicitedDistributedConnection, distributedUsername, pierceFirewall.Token), connection); } else { throw new ConnectionException($"Unknown PierceFirewall attempt with token {pierceFirewall.Token} from {connection.IPEndPoint.Address}:{connection.IPEndPoint.Port} (id: {connection.Id})"); } } else { throw new ConnectionException($"Unrecognized initialization message: {BitConverter.ToString(message)} ({message.Length} bytes, id: {connection.Id})"); } } catch (Exception ex) { Diagnostic.Debug($"Failed to initialize direct connection from {connection.IPEndPoint.Address}:{connection.IPEndPoint.Port}: {ex.Message}"); connection.Disconnect(exception: ex); connection.Dispose(); } }
/// <summary> /// Gets a new or existing message connection to the specified <paramref name="username"/>. /// </summary> /// <remarks> /// If a connection doesn't exist, new direct and indirect connections are attempted simultaneously, and the first to /// connect is returned. /// </remarks> /// <param name="username">The username of the user to which to connect.</param> /// <param name="ipEndPoint">The remote IP endpoint of the connection.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <returns>The operation context, including the new or existing connection.</returns> public async Task <IMessageConnection> GetOrAddMessageConnectionAsync(string username, IPEndPoint ipEndPoint, CancellationToken cancellationToken) { bool cached = true; try { var connection = await MessageConnectionDictionary.GetOrAdd( username, key => new Lazy <Task <IMessageConnection> >(() => GetConnection())).Value.ConfigureAwait(false); if (cached) { Diagnostic.Debug($"Retrieved cached message connection to {username} ({ipEndPoint}) (type: {connection.Type}, id: {connection.Id})"); } return(connection); } catch { Diagnostic.Debug($"Purging message connection cache of failed connection to {username} ({ipEndPoint})."); MessageConnectionDictionary.TryRemove(username, out _); throw; } async Task <IMessageConnection> GetConnection() { cached = false; using var directCts = new CancellationTokenSource(); using var directLinkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, directCts.Token); using var indirectCts = new CancellationTokenSource(); using var indirectLinkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, indirectCts.Token); Diagnostic.Debug($"Attempting simultaneous direct and indirect message connections to {username} ({ipEndPoint})"); var direct = GetMessageConnectionOutboundDirectAsync(username, ipEndPoint, directLinkedCts.Token); var indirect = GetMessageConnectionOutboundIndirectAsync(username, indirectLinkedCts.Token); var tasks = new[] { direct, indirect }.ToList(); Task <IMessageConnection> task; do { task = await Task.WhenAny(tasks).ConfigureAwait(false); tasks.Remove(task); }while (task.Status != TaskStatus.RanToCompletion && tasks.Count > 0); if (task.Status != TaskStatus.RanToCompletion) { var msg = $"Failed to establish a direct or indirect message connection to {username} ({ipEndPoint})"; Diagnostic.Debug(msg); throw new ConnectionException(msg); } var connection = await task.ConfigureAwait(false); connection.Disconnected += MessageConnection_Disconnected; var isDirect = task == direct; Diagnostic.Debug($"{(isDirect ? "Direct" : "Indirect")} message connection to {username} ({ipEndPoint}) established first, attempting to cancel {(isDirect ? "indirect" : "direct")} connection."); (isDirect ? indirectCts : directCts).Cancel(); try { if (isDirect) { var request = new PeerInit(SoulseekClient.Username, Constants.ConnectionType.Peer, SoulseekClient.GetNextToken()); await connection.WriteAsync(request, cancellationToken).ConfigureAwait(false); } else { connection.StartReadingContinuously(); } } catch (Exception ex) { var msg = $"Failed to negotiate message connection to {username} ({ipEndPoint}): {ex.Message}"; Diagnostic.Debug($"{msg} (type: {connection.Type}, id: {connection.Id})"); connection.Dispose(); throw new ConnectionException(msg, ex); } Diagnostic.Debug($"Message connection to {username} ({ipEndPoint}) established. (type: {connection.Type}, id: {connection.Id})"); return(connection); } }