internal async Task GetTransferConnectionAsync_Connects_And_Pierces_Firewall(string username, string type, IPAddress ipAddress, int port, int token, ConnectionOptions options) { var conn = new Mock <IConnection>(); conn.Setup(m => m.IPAddress) .Returns(ipAddress); conn.Setup(m => m.Port) .Returns(port); conn.Setup(m => m.ConnectAsync()) .Returns(Task.CompletedTask); conn.Setup(m => m.WriteAsync(It.IsAny <byte[]>())) .Returns(Task.CompletedTask); var connFactory = new Mock <IConnectionFactory>(); connFactory.Setup(m => m.GetConnection(It.IsAny <IPAddress>(), It.IsAny <int>(), It.IsAny <ConnectionOptions>())) .Returns(conn.Object); var response = new ConnectToPeerResponse(username, type, ipAddress, port, token); var s = new SoulseekClient("127.0.0.1", 1, connectionFactory: connFactory.Object); await s.InvokeMethod <Task <IConnection> >("GetTransferConnectionAsync", response, options, CancellationToken.None); conn.Verify(m => m.ConnectAsync(It.IsAny <CancellationToken>()), Times.Once); conn.Verify(m => m.WriteAsync(It.IsAny <byte[]>(), It.IsAny <CancellationToken>()), Times.Once); }
public async Task Completes_Start_Wait_When_Download_Exists(string username, string filename, int token, int remoteToken) { var conn = new Mock <IConnection>(); conn.Setup(m => m.ReadAsync(It.IsAny <int>(), null)) .Returns(Task.FromResult(BitConverter.GetBytes(remoteToken))); var connManager = new Mock <IConnectionManager>(); connManager.Setup(m => m.AddSolicitedTransferConnectionAsync(It.IsAny <ConnectToPeerResponse>(), It.IsAny <ConnectionOptions>(), It.IsAny <CancellationToken>())) .Returns(Task.FromResult(conn.Object)); var waiter = new Mock <IWaiter>(); var s = new SoulseekClient("127.0.0.1", 1, null, waiter: waiter.Object, connectionManager: connManager.Object); var activeDownloads = new ConcurrentDictionary <int, Download>(); var download = new Download(username, filename, token) { RemoteToken = remoteToken }; activeDownloads.TryAdd(token, download); s.SetProperty("Downloads", activeDownloads); var r = new ConnectToPeerResponse(username, "F", IPAddress.Parse("127.0.0.1"), 1, token); await s.InvokeMethod <Task>("InitializeDownloadAsync", r); waiter.Verify(m => m.Complete(download.WaitKey, It.IsAny <IConnection>()), Times.Once); }
public async Task Does_Not_Throw_On_Remote_Token_Read_Exception(string username, string filename, int token) { var conn = new Mock <IConnection>(); conn.Setup(m => m.ReadAsync(It.IsAny <int>(), It.IsAny <CancellationToken>())) .Returns(Task.FromException <byte[]>(new Exception())); var connManager = new Mock <IConnectionManager>(); connManager.Setup(m => m.AddSolicitedTransferConnectionAsync(It.IsAny <ConnectToPeerResponse>(), It.IsAny <ConnectionOptions>(), It.IsAny <CancellationToken>())) .Returns(Task.FromResult(conn.Object)); var waiter = new Mock <IWaiter>(); var s = new SoulseekClient("127.0.0.1", 1, null, waiter: waiter.Object, connectionManager: connManager.Object); var activeDownloads = new ConcurrentDictionary <int, Download>(); var download = new Download(username, filename, token); activeDownloads.TryAdd(token, download); s.SetProperty("Downloads", activeDownloads); var r = new ConnectToPeerResponse(username, "F", IPAddress.Parse("127.0.0.1"), 1, token); var ex = await Record.ExceptionAsync(async() => await s.InvokeMethod <Task>("InitializeDownloadAsync", r)); Assert.Null(ex); waiter.Verify(m => m.Complete(download.WaitKey), Times.Never); }
public async Task Transfer_Exception_Disconnects_And_Does_Not_Throw(string username, string filename, int token) { string message = null; var conn = new Mock <IConnection>(); conn.Setup(m => m.ReadAsync(It.IsAny <int>(), null)) .Returns(Task.FromException <byte[]>(new Exception("fake exception"))); conn.Setup(m => m.Disconnect(It.IsAny <string>())) .Callback <string>(str => message = str); var connManager = new Mock <IConnectionManager>(); connManager.Setup(m => m.AddSolicitedTransferConnectionAsync(It.IsAny <ConnectToPeerResponse>(), It.IsAny <ConnectionOptions>(), It.IsAny <CancellationToken>())) .Returns(Task.FromResult(conn.Object)); var waiter = new Mock <IWaiter>(); var s = new SoulseekClient("127.0.0.1", 1, null, waiter: waiter.Object, connectionManager: connManager.Object); var activeDownloads = new ConcurrentDictionary <int, Download>(); var download = new Download(username, filename, token); activeDownloads.TryAdd(token, download); s.SetProperty("Downloads", activeDownloads); var r = new ConnectToPeerResponse(username, "F", IPAddress.Parse("127.0.0.1"), 1, token); var ex = await Record.ExceptionAsync(async() => await s.InvokeMethod <Task>("InitializeDownloadAsync", r)); Assert.Null(ex); conn.Verify(m => m.Disconnect(It.IsAny <string>()), Times.Once); Assert.Contains("fake exception", message, StringComparison.InvariantCultureIgnoreCase); }
/// <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 Creates_Connection_On_ConnectToPeerResponse_P(string username, int token, IPAddress ip, int port) { ConnectToPeerResponse response = null; var connMgr = new Mock <IConnectionManager>(); connMgr .Setup(m => m.GetOrAddSolicitedConnectionAsync(It.IsAny <ConnectToPeerResponse>(), It.IsAny <EventHandler <Message> >(), It.IsAny <ConnectionOptions>(), It.IsAny <CancellationToken>())) .Returns(Task.FromResult(new Mock <IMessageConnection>().Object)) .Callback <ConnectToPeerResponse, EventHandler <Message>, ConnectionOptions, CancellationToken>((r, e, c, t) => response = r); var ipBytes = ip.GetAddressBytes(); Array.Reverse(ipBytes); var msg = new MessageBuilder() .Code(MessageCode.ServerConnectToPeer) .WriteString(username) .WriteString("P") .WriteBytes(ipBytes) .WriteInteger(port) .WriteInteger(token) .Build(); using (var s = new SoulseekClient("127.0.0.1", 1, connectionManager: connMgr.Object)) { s.InvokeMethod("ServerConnection_MessageRead", null, msg); Assert.Equal(username, response.Username); Assert.Equal(ip, response.IPAddress); Assert.Equal(port, response.Port); connMgr.Verify(m => m.GetOrAddSolicitedConnectionAsync(It.IsAny <ConnectToPeerResponse>(), It.IsAny <EventHandler <Message> >(), It.IsAny <ConnectionOptions>(), It.IsAny <CancellationToken>()), Times.Once); } }
public void Parse_Returns_Expected_Data(string username, string type, IPEndPoint endpoint, int token, bool isPrivileged) { var ipBytes = endpoint.Address.GetAddressBytes(); Array.Reverse(ipBytes); var msg = new MessageBuilder() .WriteCode(MessageCode.Server.ConnectToPeer) .WriteString(username) .WriteString(type) .WriteBytes(ipBytes) .WriteInteger(endpoint.Port) .WriteInteger(token) .WriteByte((byte)(isPrivileged ? 1 : 0)) .Build(); var response = ConnectToPeerResponse.FromByteArray(msg); Assert.Equal(username, response.Username); Assert.Equal(type, response.Type); Assert.Equal(endpoint.Address, response.IPAddress); Assert.Equal(endpoint.Port, response.Port); Assert.Equal(token, response.Token); Assert.Equal(isPrivileged, response.IsPrivileged); }
internal async Task GetTransferConnectionAsync_Returns_Expected_IConnection(string username, string type, IPAddress ipAddress, int port, int token, ConnectionOptions options) { var conn = new Mock <IConnection>(); conn.Setup(m => m.IPAddress) .Returns(ipAddress); conn.Setup(m => m.Port) .Returns(port); var connFactory = new Mock <IConnectionFactory>(); connFactory.Setup(m => m.GetConnection(It.IsAny <IPAddress>(), It.IsAny <int>(), It.IsAny <ConnectionOptions>())) .Returns(conn.Object); var response = new ConnectToPeerResponse(username, type, ipAddress, port, token); var s = new SoulseekClient("127.0.0.1", 1, connectionFactory: connFactory.Object); IConnection result = null; var ex = await Record.ExceptionAsync(async() => result = await s.InvokeMethod <Task <IConnection> >("GetTransferConnectionAsync", response, options, CancellationToken.None)); Assert.Null(ex); Assert.Equal(response.IPAddress, result.IPAddress); Assert.Equal(response.Port, result.Port); }
public void Parse_Returns_Expected_Data() { var un = RandomGuid; var type = RandomGuid; var ip = new IPAddress(Random.Next(1024)); var ipBytes = ip.GetAddressBytes(); Array.Reverse(ipBytes); var port = Random.Next(); var token = Random.Next(); var msg = new MessageBuilder() .Code(MessageCode.ServerConnectToPeer) .WriteString(un) .WriteString(type) .WriteBytes(ipBytes) .WriteInteger(port) .WriteInteger(token) .Build(); var response = ConnectToPeerResponse.Parse(msg); Assert.Equal(un, response.Username); Assert.Equal(type, response.Type); Assert.Equal(ip, response.IPAddress); Assert.Equal(port, response.Port); Assert.Equal(token, response.Token); }
public void Parse_Throws_MessageException_On_Code_Mismatch() { var msg = new MessageBuilder() .WriteCode(MessageCode.Peer.BrowseRequest) .Build(); var ex = Record.Exception(() => ConnectToPeerResponse.FromByteArray(msg)); Assert.NotNull(ex); Assert.IsType <MessageException>(ex); }
public void Parse_Throws_MessageReadException_On_Missing_Data() { var msg = new MessageBuilder() .WriteCode(MessageCode.Server.ConnectToPeer) .WriteString("foo") .WriteString("F") .Build(); var ex = Record.Exception(() => ConnectToPeerResponse.FromByteArray(msg)); Assert.NotNull(ex); Assert.IsType <MessageReadException>(ex); }
public void Instantiates_With_The_Given_Data(string username, string type, IPEndPoint endpoint, int token) { ConnectToPeerResponse response = null; var ex = Record.Exception(() => response = new ConnectToPeerResponse(username, type, endpoint, token)); Assert.Null(ex); Assert.Equal(username, response.Username); Assert.Equal(type, response.Type); Assert.Equal(endpoint.Address, response.IPEndPoint.Address); Assert.Equal(endpoint.Port, response.IPEndPoint.Port); Assert.Equal(token, response.Token); }
public async Task GetSolicitedPeerConnectionAsync_Adds_Instance_To_PeerConnectionManager(string username, IPAddress ipAddress, int port, int token) { var ctpr = new ConnectToPeerResponse(username, "P", ipAddress, port, token); var options = new ConnectionOptions(); var pcm = new Mock <IConnectionManager <IMessageConnection> >(); pcm.Setup(m => m.AddAsync(It.IsAny <IMessageConnection>())) .Returns(Task.CompletedTask); var s = new SoulseekClient("127.0.0.1", 1, peerConnectionManager: pcm.Object); await s.InvokeMethod <Task <IMessageConnection> >("GetSolicitedPeerConnectionAsync", ctpr, options, CancellationToken.None); pcm.Verify(m => m.AddAsync(It.IsAny <IMessageConnection>()), Times.Once); }
public async Task Does_Not_Throw_On_Download_Missing_From_ActiveDownloads() { var conn = new Mock <IConnection>(); conn.Setup(m => m.ReadAsync(It.IsAny <int>(), It.IsAny <CancellationToken>())) .Returns(Task.FromResult(BitConverter.GetBytes(1))); var connManager = new Mock <IConnectionManager>(); connManager.Setup(m => m.AddSolicitedTransferConnectionAsync(It.IsAny <ConnectToPeerResponse>(), It.IsAny <ConnectionOptions>(), It.IsAny <CancellationToken>())) .Returns(Task.FromResult(conn.Object)); var s = new SoulseekClient("127.0.0.1", 1, null, connectionManager: connManager.Object); var r = new ConnectToPeerResponse("username", "F", IPAddress.Parse("127.0.0.1"), 1, 1); var ex = await Record.ExceptionAsync(async() => await s.InvokeMethod <Task>("InitializeDownloadAsync", r)); Assert.Null(ex); }
public async Task GetSolicitedPeerConnectionAsync_Returns_IMessageConnection_Instance(string username, IPAddress ipAddress, int port, int token) { var ctpr = new ConnectToPeerResponse(username, "P", ipAddress, port, token); var options = new ConnectionOptions(); var s = new SoulseekClient(); IMessageConnection conn = null; var ex = await Record.ExceptionAsync(async() => conn = await s.InvokeMethod <Task <IMessageConnection> >("GetSolicitedPeerConnectionAsync", ctpr, options, CancellationToken.None)); Assert.Null(ex); Assert.NotNull(conn); Assert.Equal(username, conn.Username); Assert.Equal(ipAddress, conn.IPAddress); Assert.Equal(port, conn.Port); Assert.Equal(ctpr, conn.Context); }
public void Instantiates_With_The_Given_Data() { var un = RandomGuid; var type = RandomGuid; var ip = new IPAddress(Random.Next(1024)); var port = Random.Next(); var token = Random.Next(); ConnectToPeerResponse response = null; var ex = Record.Exception(() => response = new ConnectToPeerResponse(un, type, ip, port, token)); Assert.Null(ex); Assert.Equal(un, response.Username); Assert.Equal(type, response.Type); Assert.Equal(ip, response.IPAddress); Assert.Equal(port, response.Port); Assert.Equal(token, response.Token); }
internal async Task GetOrAddSolicitedConnectionAsync_Connects_And_Pierces_Firewall( string username, IPAddress ipAddress, int port, EventHandler <Message> messageHandler, ConnectionOptions options, int token) { var ctpr = new ConnectToPeerResponse(username, "P", ipAddress, port, token); var expectedBytes = new PierceFirewallRequest(token).ToMessage().ToByteArray(); byte[] actualBytes = Array.Empty <byte>(); var tokenFactory = new Mock <ITokenFactory>(); tokenFactory.Setup(m => m.GetToken()) .Returns(token); var conn = new Mock <IMessageConnection>(); conn.Setup(m => m.ConnectAsync(It.IsAny <CancellationToken>())) .Returns(Task.CompletedTask); conn.Setup(m => m.WriteAsync(It.IsAny <byte[]>(), It.IsAny <CancellationToken>())) .Returns(Task.CompletedTask) .Callback <byte[], CancellationToken>((b, ct) => actualBytes = b); var connFactory = new Mock <IConnectionFactory>(); connFactory.Setup(m => m.GetMessageConnection(MessageConnectionType.Peer, username, ipAddress, port, options)) .Returns(conn.Object); var c = new ConnectionManager(10, tokenFactory.Object, connFactory.Object); IMessageConnection connection = null; var ex = await Record.ExceptionAsync(async() => connection = await c.GetOrAddSolicitedConnectionAsync(ctpr, messageHandler, options, CancellationToken.None)); Assert.Null(ex); Assert.Equal(conn.Object, connection); Assert.Equal(expectedBytes, actualBytes); }
internal async Task AddSolicitedTransferConnectionAsync_Connects_And_Pierces_Firewall(string username, IPAddress ipAddress, int port, int token, ConnectionOptions options) { var ctpr = new ConnectToPeerResponse(username, "F", ipAddress, port, token); var expectedBytes = new PierceFirewallRequest(token).ToMessage().ToByteArray(); byte[] actualBytes = Array.Empty <byte>(); var conn = new Mock <IConnection>(); conn.Setup(m => m.IPAddress) .Returns(ipAddress); conn.Setup(m => m.Port) .Returns(port); conn.Setup(m => m.ConnectAsync(It.IsAny <CancellationToken>())) .Returns(Task.CompletedTask); conn.Setup(m => m.WriteAsync(It.IsAny <byte[]>(), It.IsAny <CancellationToken>())) .Returns(Task.CompletedTask) .Callback <byte[], CancellationToken>((b, c) => actualBytes = b); var connFactory = new Mock <IConnectionFactory>(); connFactory.Setup(m => m.GetConnection(It.IsAny <IPAddress>(), It.IsAny <int>(), It.IsAny <ConnectionOptions>())) .Returns(conn.Object); IConnection newConn = null; using (var c = new ConnectionManager(1, connectionFactory: connFactory.Object)) { newConn = await c.AddSolicitedTransferConnectionAsync(ctpr, options, CancellationToken.None); } Assert.Equal(ipAddress, newConn.IPAddress); Assert.Equal(port, newConn.Port); Assert.Equal(expectedBytes, actualBytes); conn.Verify(m => m.ConnectAsync(It.IsAny <CancellationToken>()), Times.Once); conn.Verify(m => m.WriteAsync(It.IsAny <byte[]>(), It.IsAny <CancellationToken>()), Times.Once); }
internal async Task GetOrAddSolicitedConnectionAsync_Returns_Existing_Connection( string username, IPAddress ipAddress, int port, EventHandler <Message> messageHandler, ConnectionOptions options, int token) { var ctpr = new ConnectToPeerResponse(username, "P", ipAddress, port, token); var key = new ConnectionKey(username, ipAddress, port, MessageConnectionType.Peer); var conn = new Mock <IMessageConnection>(); var peer = new ConcurrentDictionary <ConnectionKey, (SemaphoreSlim Semaphore, IMessageConnection Connection)>(); peer.GetOrAdd(key, (new SemaphoreSlim(1), conn.Object)); var c = new ConnectionManager(10); c.SetProperty("PeerConnections", peer); IMessageConnection connection = null; var ex = await Record.ExceptionAsync(async() => connection = await c.GetOrAddSolicitedConnectionAsync(ctpr, messageHandler, options, CancellationToken.None)); Assert.Null(ex); Assert.Equal(conn.Object, connection); }
/// <summary> /// Handles incoming messages. /// </summary> /// <param name="sender">The <see cref="IMessageConnection"/> instance from which the message originated.</param> /// <param name="message">The message.</param> public async void HandleMessageRead(object sender, byte[] message) { var code = new MessageReader <MessageCode.Server>(message).ReadCode(); if (code != MessageCode.Server.SearchRequest) { Diagnostic.Debug($"Server message received: {code}"); } try { switch (code) { case MessageCode.Server.ParentMinSpeed: case MessageCode.Server.ParentSpeedRatio: case MessageCode.Server.WishlistInterval: case MessageCode.Server.CheckPrivileges: SoulseekClient.Waiter.Complete(new WaitKey(code), IntegerResponse.FromByteArray <MessageCode.Server>(message)); break; case MessageCode.Server.NewPassword: var confirmedPassword = NewPassword.FromByteArray(message).Password; SoulseekClient.Waiter.Complete(new WaitKey(code), confirmedPassword); break; case MessageCode.Server.GlobalAdminMessage: var msg = GlobalMessageNotification.FromByteArray(message); GlobalMessageReceived?.Invoke(this, new GlobalMessageReceivedEventArgs(msg)); break; case MessageCode.Server.Ping: SoulseekClient.Waiter.Complete(new WaitKey(code)); break; case MessageCode.Server.Login: SoulseekClient.Waiter.Complete(new WaitKey(code), LoginResponse.FromByteArray(message)); break; case MessageCode.Server.RoomList: var roomList = RoomListResponse.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(code), roomList); RoomListReceived?.Invoke(this, new RoomListReceivedEventArgs(roomList)); break; case MessageCode.Server.PrivilegedUsers: var privilegedUserList = PrivilegedUserListNotification.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(code), privilegedUserList); PrivilegedUserListReceived?.Invoke(this, new PrivilegedUserListReceivedEventArgs(privilegedUserList)); break; case MessageCode.Server.AddPrivilegedUser: PrivilegeNotificationReceived?.Invoke(this, new PrivilegeNotificationReceivedEventArgs(PrivilegedUserNotification.FromByteArray(message))); break; case MessageCode.Server.NotifyPrivileges: var pn = PrivilegeNotification.FromByteArray(message); PrivilegeNotificationReceived?.Invoke(this, new PrivilegeNotificationReceivedEventArgs(pn.Username, pn.Id)); if (SoulseekClient.Options.AutoAcknowledgePrivilegeNotifications) { await SoulseekClient.AcknowledgePrivilegeNotificationAsync(pn.Id, CancellationToken.None).ConfigureAwait(false); } break; case MessageCode.Server.UserPrivileges: var privilegeResponse = UserPrivilegeResponse.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(code, privilegeResponse.Username), privilegeResponse.IsPrivileged); break; case MessageCode.Server.NetInfo: var netInfo = NetInfoNotification.FromByteArray(message); try { var parents = netInfo.Parents.Select(parent => (parent.Username, new IPEndPoint(parent.IPAddress, parent.Port))); await SoulseekClient.DistributedConnectionManager.AddParentConnectionAsync(parents).ConfigureAwait(false); } catch (Exception ex) { Diagnostic.Debug($"Error handling NetInfo message: {ex.Message}"); } break; case MessageCode.Server.ConnectToPeer: ConnectToPeerResponse connectToPeerResponse = default; try { connectToPeerResponse = ConnectToPeerResponse.FromByteArray(message); if (connectToPeerResponse.Type == Constants.ConnectionType.Transfer) { Diagnostic.Debug($"Received transfer ConnectToPeer request from {connectToPeerResponse.Username} ({connectToPeerResponse.IPEndPoint}) for remote token {connectToPeerResponse.Token}"); // ensure that we are expecting at least one file from this user before we connect. the response // doesn't contain any other identifying information about the file. if (!SoulseekClient.Downloads.IsEmpty && SoulseekClient.Downloads.Values.Any(d => d.Username == connectToPeerResponse.Username)) { var(connection, remoteToken) = await SoulseekClient.PeerConnectionManager.GetTransferConnectionAsync(connectToPeerResponse).ConfigureAwait(false); var download = SoulseekClient.Downloads.Values.FirstOrDefault(v => v.RemoteToken == remoteToken && v.Username == connectToPeerResponse.Username); if (download != default(TransferInternal)) { Diagnostic.Debug($"Solicited inbound transfer connection to {download.Username} ({connection.IPEndPoint}) for token {download.Token} (remote: {download.RemoteToken}) established. (id: {connection.Id})"); SoulseekClient.Waiter.Complete(new WaitKey(Constants.WaitKey.IndirectTransfer, download.Username, download.Filename, download.RemoteToken), connection); } else { Diagnostic.Debug($"Transfer ConnectToPeer request from {connectToPeerResponse.Username} ({connectToPeerResponse.IPEndPoint}) for remote token {connectToPeerResponse.Token} does not match any waiting downloads, discarding."); connection.Disconnect($"Unknown transfer"); } } else { throw new SoulseekClientException($"Unexpected transfer request from {connectToPeerResponse.Username} ({connectToPeerResponse.IPEndPoint}); Ignored"); } } else if (connectToPeerResponse.Type == Constants.ConnectionType.Peer) { Diagnostic.Debug($"Received message ConnectToPeer request from {connectToPeerResponse.Username} ({connectToPeerResponse.IPEndPoint})"); await SoulseekClient.PeerConnectionManager.GetOrAddMessageConnectionAsync(connectToPeerResponse).ConfigureAwait(false); } else if (connectToPeerResponse.Type == Constants.ConnectionType.Distributed) { Diagnostic.Debug($"Received distributed ConnectToPeer request from {connectToPeerResponse.Username} ({connectToPeerResponse.IPEndPoint})"); await SoulseekClient.DistributedConnectionManager.AddChildConnectionAsync(connectToPeerResponse).ConfigureAwait(false); } else { throw new MessageException($"Unknown Connect To Peer connection type '{connectToPeerResponse.Type}'"); } } catch (Exception ex) { Diagnostic.Debug($"Error handling ConnectToPeer response from {connectToPeerResponse?.Username} ({connectToPeerResponse?.IPEndPoint}): {ex.Message}"); } break; case MessageCode.Server.AddUser: var addUserResponse = AddUserResponse.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(code, addUserResponse.Username), addUserResponse); break; case MessageCode.Server.GetStatus: var statsResponse = UserStatusResponse.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(code, statsResponse.Username), statsResponse); UserStatusChanged?.Invoke(this, new UserStatusChangedEventArgs(statsResponse)); break; case MessageCode.Server.PrivateMessage: var pm = PrivateMessageNotification.FromByteArray(message); PrivateMessageReceived?.Invoke(this, new PrivateMessageReceivedEventArgs(pm)); if (SoulseekClient.Options.AutoAcknowledgePrivateMessages) { await SoulseekClient.AcknowledgePrivateMessageAsync(pm.Id, CancellationToken.None).ConfigureAwait(false); } break; case MessageCode.Server.GetPeerAddress: var peerAddressResponse = UserAddressResponse.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(code, peerAddressResponse.Username), peerAddressResponse); break; case MessageCode.Server.JoinRoom: var roomData = RoomJoinResponse.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(code, roomData.Name), roomData); break; case MessageCode.Server.LeaveRoom: var leaveRoomResponse = RoomLeaveResponse.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(code, leaveRoomResponse.RoomName)); break; case MessageCode.Server.SayInChatRoom: var roomMessage = RoomMessageNotification.FromByteArray(message); RoomMessageReceived?.Invoke(this, new RoomMessageReceivedEventArgs(roomMessage)); break; case MessageCode.Server.UserJoinedRoom: var joinNotification = RoomJoinedNotification.FromByteArray(message); RoomJoined?.Invoke(this, new RoomJoinedEventArgs(joinNotification)); break; case MessageCode.Server.UserLeftRoom: var leftNotification = RoomLeftNotification.FromByteArray(message); RoomLeft?.Invoke(this, new RoomLeftEventArgs(leftNotification)); break; case MessageCode.Server.KickedFromServer: KickedFromServer?.Invoke(this, EventArgs.Empty); break; case MessageCode.Server.FileSearch: var searchRequest = ServerSearchRequest.FromByteArray(message); // sometimes (most of the time?) a room search will result in a request to ourselves (assuming we are // joined to it) if (searchRequest.Username == SoulseekClient.Username) { break; } SearchResponse searchResponse; if (SoulseekClient.Options.SearchResponseResolver == default) { break; } try { searchResponse = await SoulseekClient.Options.SearchResponseResolver(searchRequest.Username, searchRequest.Token, SearchQuery.FromText(searchRequest.Query)).ConfigureAwait(false); if (searchResponse != null && searchResponse.FileCount + searchResponse.LockedFileCount > 0) { var endpoint = await SoulseekClient.GetUserEndPointAsync(searchRequest.Username).ConfigureAwait(false); var peerConnection = await SoulseekClient.PeerConnectionManager.GetOrAddMessageConnectionAsync(searchRequest.Username, endpoint, CancellationToken.None).ConfigureAwait(false); await peerConnection.WriteAsync(searchResponse.ToByteArray()).ConfigureAwait(false); } } catch (Exception ex) { Diagnostic.Warning($"Error resolving search response for query '{searchRequest.Query}' requested by {searchRequest.Username} with token {searchRequest.Token}: {ex.Message}", ex); } break; // if we fail to connect to a distributed parent in a timely manner, the server will begin to send us distributed search requests directly. // forward these to the distributed message handler. case MessageCode.Server.SearchRequest: SoulseekClient.DistributedMessageHandler.HandleMessageRead(SoulseekClient.ServerConnection, message); break; default: Diagnostic.Debug($"Unhandled server message: {code}; {message.Length} bytes"); break; } } catch (Exception ex) { Diagnostic.Warning($"Error handling server message: {code}; {ex.Message}", ex); } }
/// <summary> /// Handles incoming messages. /// </summary> /// <param name="sender">The <see cref="IMessageConnection"/> instance from which the message originated.</param> /// <param name="message">The message.</param> public async void HandleMessage(object sender, byte[] message) { var code = new MessageReader <MessageCode.Server>(message).ReadCode(); Diagnostic.Debug($"Server message received: {code}"); try { switch (code) { case MessageCode.Server.ParentMinSpeed: case MessageCode.Server.ParentSpeedRatio: case MessageCode.Server.WishlistInterval: SoulseekClient.Waiter.Complete(new WaitKey(code), IntegerResponse.FromByteArray <MessageCode.Server>(message)); break; case MessageCode.Server.Login: SoulseekClient.Waiter.Complete(new WaitKey(code), LoginResponse.FromByteArray(message)); break; case MessageCode.Server.RoomList: SoulseekClient.Waiter.Complete(new WaitKey(code), RoomList.FromByteArray(message)); break; case MessageCode.Server.PrivilegedUsers: SoulseekClient.Waiter.Complete(new WaitKey(code), PrivilegedUserList.FromByteArray(message)); break; case MessageCode.Server.NetInfo: var netInfo = NetInfo.FromByteArray(message); try { await SoulseekClient.DistributedConnectionManager.AddParentConnectionAsync(netInfo.Parents).ConfigureAwait(false); } catch (Exception ex) { Diagnostic.Debug($"Error handling NetInfo message: {ex.Message}"); } break; case MessageCode.Server.ConnectToPeer: ConnectToPeerResponse connectToPeerResponse = default; try { connectToPeerResponse = ConnectToPeerResponse.FromByteArray(message); if (connectToPeerResponse.Type == Constants.ConnectionType.Transfer) { // ensure that we are expecting at least one file from this user before we connect. the response // doesn't contain any other identifying information about the file. if (!SoulseekClient.Downloads.IsEmpty && SoulseekClient.Downloads.Values.Any(d => d.Username == connectToPeerResponse.Username)) { var(connection, remoteToken) = await SoulseekClient.PeerConnectionManager.GetTransferConnectionAsync(connectToPeerResponse).ConfigureAwait(false); var download = SoulseekClient.Downloads.Values.FirstOrDefault(v => v.RemoteToken == remoteToken && v.Username == connectToPeerResponse.Username); if (download != default(Transfer)) { SoulseekClient.Waiter.Complete(new WaitKey(Constants.WaitKey.IndirectTransfer, download.Username, download.Filename, download.RemoteToken), connection); } } else { throw new SoulseekClientException($"Unexpected transfer request from {connectToPeerResponse.Username} ({connectToPeerResponse.IPAddress}:{connectToPeerResponse.Port}); Ignored"); } } else if (connectToPeerResponse.Type == Constants.ConnectionType.Peer) { await SoulseekClient.PeerConnectionManager.GetOrAddMessageConnectionAsync(connectToPeerResponse).ConfigureAwait(false); } else if (connectToPeerResponse.Type == Constants.ConnectionType.Distributed) { await SoulseekClient.DistributedConnectionManager.AddChildConnectionAsync(connectToPeerResponse).ConfigureAwait(false); } else { throw new MessageException($"Unknown Connect To Peer connection type '{connectToPeerResponse.Type}'"); } } catch (Exception ex) { Diagnostic.Debug($"Error handling ConnectToPeer response from {connectToPeerResponse?.Username} ({connectToPeerResponse?.IPAddress}:{connectToPeerResponse.Port}): {ex.Message}"); } break; case MessageCode.Server.AddUser: var addUserResponse = AddUserResponse.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(code, addUserResponse.Username), addUserResponse); break; case MessageCode.Server.GetStatus: var statsResponse = GetStatusResponse.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(code, statsResponse.Username), statsResponse); UserStatusChanged?.Invoke(this, new UserStatusChangedEventArgs(statsResponse)); break; case MessageCode.Server.PrivateMessage: var pm = PrivateMessage.FromByteArray(message); PrivateMessageReceived?.Invoke(this, pm); if (SoulseekClient.Options.AutoAcknowledgePrivateMessages) { await SoulseekClient.AcknowledgePrivateMessageAsync(pm.Id, CancellationToken.None).ConfigureAwait(false); } break; case MessageCode.Server.GetPeerAddress: var peerAddressResponse = GetPeerAddressResponse.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(code, peerAddressResponse.Username), peerAddressResponse); break; default: Diagnostic.Debug($"Unhandled server message: {code}; {message.Length} bytes"); break; } } catch (Exception ex) { Diagnostic.Warning($"Error handling server message: {code}; {ex.Message}", ex); } }
/// <summary> /// Handles incoming messages. /// </summary> /// <param name="sender">The <see cref="IMessageConnection"/> instance from which the message originated.</param> /// <param name="message">The message.</param> public async void HandleMessageRead(object sender, byte[] message) { var code = new MessageReader <MessageCode.Server>(message).ReadCode(); if (code != MessageCode.Server.EmbeddedMessage) { Diagnostic.Debug($"Server message received: {code}"); } try { switch (code) { case MessageCode.Server.ParentMinSpeed: case MessageCode.Server.ParentSpeedRatio: case MessageCode.Server.WishlistInterval: case MessageCode.Server.CheckPrivileges: SoulseekClient.Waiter.Complete(new WaitKey(code), IntegerResponse.FromByteArray <MessageCode.Server>(message)); break; case MessageCode.Server.PrivateRoomAdded: PrivateRoomMembershipAdded?.Invoke(this, StringResponse.FromByteArray <MessageCode.Server>(message)); break; case MessageCode.Server.PrivateRoomRemoved: var privateRoomRemoved = StringResponse.FromByteArray <MessageCode.Server>(message); SoulseekClient.Waiter.Complete(new WaitKey(code, privateRoomRemoved)); PrivateRoomMembershipRemoved?.Invoke(this, privateRoomRemoved); break; case MessageCode.Server.PrivateRoomOperatorAdded: PrivateRoomModerationAdded?.Invoke(this, StringResponse.FromByteArray <MessageCode.Server>(message)); break; case MessageCode.Server.PrivateRoomOperatorRemoved: var privateRoomOperatorRemoved = StringResponse.FromByteArray <MessageCode.Server>(message); SoulseekClient.Waiter.Complete(new WaitKey(code, privateRoomOperatorRemoved)); PrivateRoomModerationRemoved?.Invoke(this, privateRoomOperatorRemoved); break; case MessageCode.Server.NewPassword: var confirmedPassword = NewPassword.FromByteArray(message).Password; SoulseekClient.Waiter.Complete(new WaitKey(code), confirmedPassword); break; case MessageCode.Server.PrivateRoomToggle: var acceptInvitations = PrivateRoomToggle.FromByteArray(message).AcceptInvitations; SoulseekClient.Waiter.Complete(new WaitKey(code), acceptInvitations); break; case MessageCode.Server.GlobalAdminMessage: var msg = GlobalMessageNotification.FromByteArray(message); GlobalMessageReceived?.Invoke(this, msg); break; case MessageCode.Server.Ping: SoulseekClient.Waiter.Complete(new WaitKey(code)); break; case MessageCode.Server.Login: SoulseekClient.Waiter.Complete(new WaitKey(code), LoginResponse.FromByteArray(message)); break; case MessageCode.Server.RoomList: var roomList = RoomListResponse.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(code), roomList); RoomListReceived?.Invoke(this, roomList); break; case MessageCode.Server.PrivateRoomOwned: var moderatedRoomInfo = PrivateRoomOwnedListNotification.FromByteArray(message); PrivateRoomModeratedUserListReceived?.Invoke(this, moderatedRoomInfo); break; case MessageCode.Server.PrivateRoomUsers: var roomInfo = PrivateRoomUserListNotification.FromByteArray(message); PrivateRoomUserListReceived?.Invoke(this, roomInfo); break; case MessageCode.Server.PrivilegedUsers: var privilegedUserList = PrivilegedUserListNotification.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(code), privilegedUserList); PrivilegedUserListReceived?.Invoke(this, privilegedUserList); break; case MessageCode.Server.AddPrivilegedUser: PrivilegeNotificationReceived?.Invoke(this, new PrivilegeNotificationReceivedEventArgs(PrivilegedUserNotification.FromByteArray(message))); break; case MessageCode.Server.NotifyPrivileges: var pn = PrivilegeNotification.FromByteArray(message); PrivilegeNotificationReceived?.Invoke(this, new PrivilegeNotificationReceivedEventArgs(pn.Username, pn.Id)); if (SoulseekClient.Options.AutoAcknowledgePrivilegeNotifications) { await SoulseekClient.AcknowledgePrivilegeNotificationAsync(pn.Id, CancellationToken.None).ConfigureAwait(false); } break; case MessageCode.Server.UserPrivileges: var privilegeResponse = UserPrivilegeResponse.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(code, privilegeResponse.Username), privilegeResponse.IsPrivileged); break; case MessageCode.Server.NetInfo: var netInfo = NetInfoNotification.FromByteArray(message); try { var parents = netInfo.Parents.Select(parent => (parent.Username, new IPEndPoint(parent.IPAddress, parent.Port))); await SoulseekClient.DistributedConnectionManager.AddParentConnectionAsync(parents).ConfigureAwait(false); } catch (Exception ex) { Diagnostic.Debug($"Error handling NetInfo message: {ex.Message}"); } break; case MessageCode.Server.CannotConnect: var cannotConnect = CannotConnect.FromByteArray(message); Diagnostic.Debug($"Received CannotConnect message for token {cannotConnect.Token}{(!string.IsNullOrEmpty(cannotConnect.Username) ? $" from user {cannotConnect.Username}" : string.Empty)}"); SoulseekClient.SearchResponder.TryDiscard(cannotConnect.Token); if (!string.IsNullOrEmpty(cannotConnect.Username)) { UserCannotConnect?.Invoke(this, new UserCannotConnectEventArgs(cannotConnect)); } break; case MessageCode.Server.CannotJoinRoom: var cannotJoinRoom = CannotJoinRoom.FromByteArray(message); SoulseekClient.Waiter.Throw( new WaitKey(MessageCode.Server.JoinRoom, cannotJoinRoom.RoomName), new RoomJoinForbiddenException($"The server rejected the request to join room {cannotJoinRoom.RoomName}")); break; case MessageCode.Server.ConnectToPeer: var connectToPeerResponse = ConnectToPeerResponse.FromByteArray(message); try { if (connectToPeerResponse.Type == Constants.ConnectionType.Transfer) { Diagnostic.Debug($"Received transfer ConnectToPeer request from {connectToPeerResponse.Username} ({connectToPeerResponse.IPEndPoint}) for remote token {connectToPeerResponse.Token}"); // ensure that we are expecting at least one file from this user before we connect. the response // doesn't contain any other identifying information about the file. if (!SoulseekClient.Downloads.IsEmpty && SoulseekClient.Downloads.Values.Any(d => d.Username == connectToPeerResponse.Username)) { var(connection, remoteToken) = await SoulseekClient.PeerConnectionManager.GetTransferConnectionAsync(connectToPeerResponse).ConfigureAwait(false); var download = SoulseekClient.Downloads.Values.FirstOrDefault(v => v.RemoteToken == remoteToken && v.Username == connectToPeerResponse.Username); if (download != default(TransferInternal)) { Diagnostic.Debug($"Solicited inbound transfer connection to {download.Username} ({connection.IPEndPoint}) for token {download.Token} (remote: {download.RemoteToken}) established. (id: {connection.Id})"); SoulseekClient.Waiter.Complete(new WaitKey(Constants.WaitKey.IndirectTransfer, download.Username, download.Filename, download.RemoteToken), connection); } else { Diagnostic.Debug($"Transfer ConnectToPeer request from {connectToPeerResponse.Username} ({connectToPeerResponse.IPEndPoint}) for remote token {connectToPeerResponse.Token} does not match any waiting downloads, discarding."); connection.Disconnect("Unknown transfer"); } } else { throw new SoulseekClientException($"Unexpected transfer request from {connectToPeerResponse.Username} ({connectToPeerResponse.IPEndPoint}); Ignored"); } } else if (connectToPeerResponse.Type == Constants.ConnectionType.Peer) { Diagnostic.Debug($"Received message ConnectToPeer request from {connectToPeerResponse.Username} ({connectToPeerResponse.IPEndPoint})"); await SoulseekClient.PeerConnectionManager.GetOrAddMessageConnectionAsync(connectToPeerResponse).ConfigureAwait(false); } else if (connectToPeerResponse.Type == Constants.ConnectionType.Distributed) { Diagnostic.Debug($"Received distributed ConnectToPeer request from {connectToPeerResponse.Username} ({connectToPeerResponse.IPEndPoint})"); await SoulseekClient.DistributedConnectionManager.GetOrAddChildConnectionAsync(connectToPeerResponse).ConfigureAwait(false); } else { throw new MessageException($"Unknown Connect To Peer connection type '{connectToPeerResponse.Type}'"); } } catch (Exception ex) { Diagnostic.Debug($"Error handling ConnectToPeer response from {connectToPeerResponse.Username} ({connectToPeerResponse.IPEndPoint}): {ex.Message}"); } break; case MessageCode.Server.AddUser: var addUserResponse = AddUserResponse.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(code, addUserResponse.Username), addUserResponse); break; case MessageCode.Server.GetStatus: var statsResponse = UserStatusResponse.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(code, statsResponse.Username), statsResponse); UserStatusChanged?.Invoke(this, new UserStatusChangedEventArgs(statsResponse)); break; case MessageCode.Server.PrivateMessage: var pm = PrivateMessageNotification.FromByteArray(message); PrivateMessageReceived?.Invoke(this, new PrivateMessageReceivedEventArgs(pm)); if (SoulseekClient.Options.AutoAcknowledgePrivateMessages) { await SoulseekClient.AcknowledgePrivateMessageAsync(pm.Id, CancellationToken.None).ConfigureAwait(false); } break; case MessageCode.Server.GetPeerAddress: var peerAddressResponse = UserAddressResponse.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(code, peerAddressResponse.Username), peerAddressResponse); break; case MessageCode.Server.JoinRoom: var roomData = JoinRoomResponse.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(code, roomData.Name), roomData); break; case MessageCode.Server.LeaveRoom: var leaveRoomResponse = LeaveRoomResponse.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(code, leaveRoomResponse.RoomName)); break; case MessageCode.Server.SayInChatRoom: var roomMessage = RoomMessageNotification.FromByteArray(message); RoomMessageReceived?.Invoke(this, new RoomMessageReceivedEventArgs(roomMessage)); break; case MessageCode.Server.PublicChat: var publicChatMessage = PublicChatMessageNotification.FromByteArray(message); PublicChatMessageReceived?.Invoke(this, new PublicChatMessageReceivedEventArgs(publicChatMessage)); break; case MessageCode.Server.UserJoinedRoom: var joinNotification = UserJoinedRoomNotification.FromByteArray(message); RoomJoined?.Invoke(this, new RoomJoinedEventArgs(joinNotification)); break; case MessageCode.Server.UserLeftRoom: var leftNotification = UserLeftRoomNotification.FromByteArray(message); RoomLeft?.Invoke(this, new RoomLeftEventArgs(leftNotification)); break; case MessageCode.Server.RoomTickers: var roomTickers = RoomTickerListNotification.FromByteArray(message); RoomTickerListReceived?.Invoke(this, new RoomTickerListReceivedEventArgs(roomTickers)); break; case MessageCode.Server.RoomTickerAdd: var roomTickerAdded = RoomTickerAddedNotification.FromByteArray(message); RoomTickerAdded?.Invoke(this, new RoomTickerAddedEventArgs(roomTickerAdded.RoomName, roomTickerAdded.Ticker)); break; case MessageCode.Server.RoomTickerRemove: var roomTickerRemoved = RoomTickerRemovedNotification.FromByteArray(message); RoomTickerRemoved?.Invoke(this, new RoomTickerRemovedEventArgs(roomTickerRemoved.RoomName, roomTickerRemoved.Username)); break; case MessageCode.Server.PrivateRoomAddUser: var privateRoomAddUserResponse = PrivateRoomAddUser.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(code, privateRoomAddUserResponse.RoomName, privateRoomAddUserResponse.Username)); break; case MessageCode.Server.PrivateRoomRemoveUser: var privateRoomRemoveUserResponse = PrivateRoomRemoveUser.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(code, privateRoomRemoveUserResponse.RoomName, privateRoomRemoveUserResponse.Username)); break; case MessageCode.Server.PrivateRoomAddOperator: var privateRoomAddOperatorResponse = PrivateRoomAddOperator.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(code, privateRoomAddOperatorResponse.RoomName, privateRoomAddOperatorResponse.Username)); break; case MessageCode.Server.PrivateRoomRemoveOperator: var privateRoomRemoveOperatorResponse = PrivateRoomRemoveOperator.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(code, privateRoomRemoveOperatorResponse.RoomName, privateRoomRemoveOperatorResponse.Username)); break; case MessageCode.Server.KickedFromServer: KickedFromServer?.Invoke(this, EventArgs.Empty); break; case MessageCode.Server.FileSearch: var searchRequest = ServerSearchRequest.FromByteArray(message); // sometimes (most of the time?) a room search will result in a request to ourselves (assuming we are // joined to it) if (searchRequest.Username == SoulseekClient.Username) { break; } await SoulseekClient.SearchResponder.TryRespondAsync(searchRequest.Username, searchRequest.Token, searchRequest.Query).ConfigureAwait(false); break; case MessageCode.Server.EmbeddedMessage: SoulseekClient.DistributedMessageHandler.HandleEmbeddedMessage(message); break; default: Diagnostic.Debug($"Unhandled server message: {code}; {message.Length} bytes"); break; } } catch (Exception ex) { Diagnostic.Warning($"Error handling server message: {code}; {ex.Message}", ex); } }
/// <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, 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> /// 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); }