public void Creates_Diagnostic_On_Distributed_PierceFirewall(IPEndPoint endpoint, string username, int token) { var(handler, mocks) = GetFixture(endpoint); var message = new PierceFirewall(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 dict = new ConcurrentDictionary <int, string>(); dict.TryAdd(token, username); mocks.DistributedConnectionManager.Setup(m => m.PendingSolicitations) .Returns(dict); handler.HandleConnection(null, mocks.Connection.Object); var compare = StringComparison.InvariantCultureIgnoreCase; mocks.Diagnostic.Verify(m => m.Debug(It.Is <string>(s => s.Contains("Distributed PierceFirewall", compare))), Times.Once); }
public void Completes_Solicited_Distributed_Connection_On_Distributed_PierceFirewall(IPEndPoint endpoint, string username, int token) { var(handler, mocks) = GetFixture(endpoint); var message = new PierceFirewall(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 dict = new ConcurrentDictionary <int, string>(); dict.TryAdd(token, username); mocks.DistributedConnectionManager.Setup(m => m.PendingSolicitations) .Returns(dict); handler.HandleConnection(null, mocks.Connection.Object); var expectedKey = new WaitKey(Constants.WaitKey.SolicitedDistributedConnection, username, token); mocks.Waiter.Verify(m => m.Complete(expectedKey, mocks.Connection.Object), Times.Once); }
/// <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); }
public void PierceFirewallRequest_Instantiates_Properly() { var token = new Random().Next(); var a = new PierceFirewall(token); Assert.Equal(token, a.Token); }
/// <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 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.PeerInit); var r = PierceFirewall.TryFromByteArray(msg.ToArray(), out var result); Assert.False(r); Assert.Null(result); }
public void PierceFirewallRequest_Constructs_The_Correct_Message() { var token = new Random().Next(); var a = new PierceFirewall(token); var msg = a.ToByteArray(); var reader = new MessageReader <MessageCode.Initialization>(msg); var code = reader.ReadCode(); Assert.Equal(MessageCode.Initialization.PierceFirewall, code); Assert.Equal(4 + 1 + 4, msg.Length); Assert.Equal(token, reader.ReadInteger()); }
public void TryParse_Returns_Expected_Data(int token) { var msg = new List <byte>(); msg.AddRange(BitConverter.GetBytes(0)); // overall length, ignored for this test. msg.Add((byte)MessageCode.Initialization.PierceFirewall); msg.AddRange(BitConverter.GetBytes(token)); // omit token var r = PierceFirewall.TryFromByteArray(msg.ToArray(), out var result); Assert.True(r); Assert.NotNull(result); Assert.Equal(token, result.Token); }
public void Responds_To_Search_On_SearchResponse_PierceFirewall(IPEndPoint endpoint, string username, int token, string query) { (string Username, int Token, string Query, SearchResponse SearchResponse)response = (username, token, query, null); var cache = new Mock <ISearchResponseCache>(); cache.Setup(m => m.TryGet(token, out response)).Returns(true); var(handler, mocks) = GetFixture(endpoint, new SoulseekClientOptions(searchResponseCache: cache.Object)); var message = new PierceFirewall(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.SearchResponder.Verify(m => m.TryRespondAsync(token), Times.Once); }
/// <summary> /// Creates a new instance of <see cref="PierceFirewall"/> 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 PierceFirewall response) { response = null; try { var reader = new MessageReader <MessageCode.Initialization>(bytes); if (reader.ReadCode() != MessageCode.Initialization.PierceFirewall) { return(false); } var token = reader.ReadInteger(); response = new PierceFirewall(token); return(true); } catch { return(false); } }
public void Creates_Diagnostic_On_PierceFirewall(IPEndPoint endpoint, int token) { var(handler, mocks) = GetFixture(endpoint); mocks.Connection.Setup(m => m.ReadAsync(4, It.IsAny <CancellationToken?>())) .Throws(new Exception()); mocks.Diagnostic.Setup(m => m.Debug(It.IsAny <string>())); var message = new PierceFirewall(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("Unknown PierceFirewall", compare))), 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> /// 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); } }
/// <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); } }
public void Instantiates_With_The_Given_Data(int token) { var r = new PierceFirewall(token); Assert.Equal(token, r.Token); }