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 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 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 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); }
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> /// 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); } }
/// <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.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 _); } } Diagnostic.Debug($"Message connection to {r.Username} ({r.IPEndPoint}) established. (type: {connection.Type}, id: {connection.Id})"); return(connection); } }