public void Instantiates_With_The_Given_Data(string filename) { QueueDownloadRequest response = null; var ex = Record.Exception(() => response = new QueueDownloadRequest(filename)); Assert.Null(ex); Assert.Equal(filename, response.Filename); }
public void Parse_Returns_Expected_Data(string filename) { var msg = new MessageBuilder() .WriteCode(MessageCode.Peer.QueueDownload) .WriteString(filename) .Build(); var response = QueueDownloadRequest.FromByteArray(msg); Assert.Equal(filename, response.Filename); }
public void Parse_Throws_MessageReadException_On_Missing_Data() { var msg = new MessageBuilder() .WriteCode(MessageCode.Peer.QueueDownload) .Build(); var ex = Record.Exception(() => QueueDownloadRequest.FromByteArray(msg)); Assert.NotNull(ex); Assert.IsType <MessageReadException>(ex); }
public void ToByteArray_Returns_Expected_Data(string filename) { var a = new QueueDownloadRequest(filename); var msg = a.ToByteArray(); var reader = new MessageReader <MessageCode.Peer>(msg); var code = reader.ReadCode(); Assert.Equal(MessageCode.Peer.QueueDownload, code); // length + code + filename len + filename Assert.Equal(4 + 4 + 4 + filename.Length, msg.Length); Assert.Equal(filename, reader.ReadString()); }
public void Does_Not_Write_PlaceInQueueResponse_On_Successful_Enqueue_Via_QueueDownload_If_PlaceInQueueResponse_Is_Null(string username, IPEndPoint endpoint, string filename) { var options = new SoulseekClientOptions( enqueueDownloadAction: (u, f, i) => Task.CompletedTask, placeInQueueResponseResolver: (u, f, i) => Task.FromResult <int?>(null)); var(handler, mocks) = GetFixture(username, endpoint, options); var message = new QueueDownloadRequest(filename).ToByteArray(); handler.HandleMessageRead(mocks.PeerConnection.Object, message); mocks.PeerConnection .Verify(m => m.WriteAsync(It.IsAny <IOutgoingMessage>(), It.IsAny <CancellationToken?>()), Times.Never); }
public void Creates_Diagnostic_On_Failed_QueueDownload_Invocation_Via_QueueDownload(string username, IPEndPoint endpoint, string filename) { var options = new SoulseekClientOptions(enqueueDownloadAction: (u, f, i) => { throw new Exception(); }); List <string> messages = new List <string>(); var(handler, mocks) = GetFixture(username, endpoint, options); mocks.Diagnostic.Setup(m => m.Warning(It.IsAny <string>(), It.IsAny <Exception>())) .Callback <string, Exception>((msg, ex) => messages.Add(msg)); var message = new QueueDownloadRequest(filename).ToByteArray(); handler.HandleMessageRead(mocks.PeerConnection.Object, message); Assert.Contains(messages, m => m.IndexOf("Failed to invoke QueueDownload action", StringComparison.InvariantCultureIgnoreCase) > -1); }
public async Task <IActionResult> Enqueue([FromRoute, Required] string username, [FromBody] QueueDownloadRequest request) { try { var waitUntilEnqueue = new TaskCompletionSource <bool>(TaskCreationOptions.RunContinuationsAsynchronously); var stream = GetLocalFileStream(request.Filename, OutputDirectory); var cts = new CancellationTokenSource(); var downloadTask = Client.DownloadAsync(username, request.Filename, () => stream, request.Size, 0, request.Token, new TransferOptions(disposeOutputStreamOnCompletion: true, stateChanged: (e) => { Tracker.AddOrUpdate(e, cts); if (e.Transfer.State.HasFlag(TransferStates.Queued) || e.Transfer.State == TransferStates.Initializing) { waitUntilEnqueue.TrySetResult(true); } }, progressUpdated: (e) => Tracker.AddOrUpdate(e, cts)), cts.Token); // wait until either the waitUntilEnqueue task completes because the download was successfully queued, or the // downloadTask throws due to an error prior to successfully queueing. var task = await Task.WhenAny(waitUntilEnqueue.Task, downloadTask); if (task == downloadTask && downloadTask.Exception is AggregateException) { var rejected = downloadTask.Exception?.InnerExceptions.Where(e => e is TransferRejectedException) ?? Enumerable.Empty <Exception>(); if (rejected.Any()) { return(StatusCode(403, rejected.First().Message)); } return(StatusCode(500, downloadTask.Exception.Message)); } // if it didn't throw, just return ok. the download will continue waiting in the background. return(StatusCode(201)); } catch (Exception ex) { return(StatusCode(500, ex.Message)); } }
/// <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 connection = (IMessageConnection)sender; var code = new MessageReader <MessageCode.Peer>(message).ReadCode(); Diagnostic.Debug($"Peer message received: {code} from {connection.Username} ({connection.IPEndPoint}) (id: {connection.Id})"); try { switch (code) { case MessageCode.Peer.SearchResponse: var searchResponse = SearchResponseFactory.FromByteArray(message); if (SoulseekClient.Searches.TryGetValue(searchResponse.Token, out var search)) { search.TryAddResponse(searchResponse); } break; case MessageCode.Peer.BrowseResponse: var browseWaitKey = new WaitKey(MessageCode.Peer.BrowseResponse, connection.Username); try { SoulseekClient.Waiter.Complete(browseWaitKey, BrowseResponseFactory.FromByteArray(message)); } catch (Exception ex) { SoulseekClient.Waiter.Throw(browseWaitKey, new MessageReadException("The peer returned an invalid browse response", ex)); throw; } break; case MessageCode.Peer.InfoRequest: UserInfo outgoingInfo; try { outgoingInfo = await SoulseekClient.Options .UserInfoResolver(connection.Username, connection.IPEndPoint).ConfigureAwait(false); } catch (Exception ex) { outgoingInfo = await new SoulseekClientOptions() .UserInfoResolver(connection.Username, connection.IPEndPoint).ConfigureAwait(false); Diagnostic.Warning($"Failed to resolve user info response: {ex.Message}", ex); } await connection.WriteAsync(outgoingInfo.ToByteArray()).ConfigureAwait(false); break; case MessageCode.Peer.SearchRequest: var searchRequest = PeerSearchRequest.FromByteArray(message); if (SoulseekClient.Options.SearchResponseResolver == default) { break; } try { var peerSearchResponse = await SoulseekClient.Options.SearchResponseResolver(connection.Username, searchRequest.Token, SearchQuery.FromText(searchRequest.Query)).ConfigureAwait(false); if (peerSearchResponse != null && peerSearchResponse.FileCount + peerSearchResponse.LockedFileCount > 0) { await connection.WriteAsync(peerSearchResponse.ToByteArray()).ConfigureAwait(false); } } catch (Exception ex) { Diagnostic.Warning($"Error resolving search response for query '{searchRequest.Query}' requested by {connection.Username} with token {searchRequest.Token}: {ex.Message}", ex); } break; case MessageCode.Peer.BrowseRequest: BrowseResponse browseResponse; try { browseResponse = await SoulseekClient.Options.BrowseResponseResolver(connection.Username, connection.IPEndPoint).ConfigureAwait(false); } catch (Exception ex) { browseResponse = await new SoulseekClientOptions() .BrowseResponseResolver(connection.Username, connection.IPEndPoint).ConfigureAwait(false); Diagnostic.Warning($"Failed to resolve browse response: {ex.Message}", ex); } await connection.WriteAsync(browseResponse.ToByteArray()).ConfigureAwait(false); break; case MessageCode.Peer.FolderContentsRequest: var folderContentsRequest = FolderContentsRequest.FromByteArray(message); Directory outgoingFolderContents = null; try { outgoingFolderContents = await SoulseekClient.Options.DirectoryContentsResolver( connection.Username, connection.IPEndPoint, folderContentsRequest.Token, folderContentsRequest.DirectoryName).ConfigureAwait(false); } catch (Exception ex) { Diagnostic.Warning($"Failed to resolve directory contents response: {ex.Message}", ex); } if (outgoingFolderContents != null) { var folderContentsResponseMessage = new FolderContentsResponse(folderContentsRequest.Token, outgoingFolderContents); await connection.WriteAsync(folderContentsResponseMessage).ConfigureAwait(false); } break; case MessageCode.Peer.FolderContentsResponse: var folderContentsResponse = FolderContentsResponse.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(MessageCode.Peer.FolderContentsResponse, connection.Username, folderContentsResponse.Token), folderContentsResponse.Directory); break; case MessageCode.Peer.InfoResponse: var incomingInfo = UserInfoResponseFactory.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(MessageCode.Peer.InfoResponse, connection.Username), incomingInfo); break; case MessageCode.Peer.TransferResponse: var transferResponse = TransferResponse.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(MessageCode.Peer.TransferResponse, connection.Username, transferResponse.Token), transferResponse); break; case MessageCode.Peer.QueueDownload: var queueDownloadRequest = QueueDownloadRequest.FromByteArray(message); var(queueRejected, queueRejectionMessage) = await TryEnqueueDownloadAsync(connection.Username, connection.IPEndPoint, queueDownloadRequest.Filename).ConfigureAwait(false); if (queueRejected) { await connection.WriteAsync(new UploadDenied(queueDownloadRequest.Filename, queueRejectionMessage)).ConfigureAwait(false); } else { await TrySendPlaceInQueueAsync(connection, queueDownloadRequest.Filename).ConfigureAwait(false); } break; case MessageCode.Peer.TransferRequest: var transferRequest = TransferRequest.FromByteArray(message); if (transferRequest.Direction == TransferDirection.Upload) { if (!SoulseekClient.DownloadDictionary.IsEmpty && SoulseekClient.DownloadDictionary.Values.Any(d => d.Username == connection.Username && d.Filename == transferRequest.Filename)) { SoulseekClient.Waiter.Complete(new WaitKey(MessageCode.Peer.TransferRequest, connection.Username, transferRequest.Filename), transferRequest); } else { // reject the transfer with an empty reason. it was probably cancelled, but we can't be sure. Diagnostic.Debug($"Rejecting unknown upload from {connection.Username} for {transferRequest.Filename} with token {transferRequest.Token}"); await connection.WriteAsync(new TransferResponse(transferRequest.Token, "Cancelled")).ConfigureAwait(false); } } else { var(transferRejected, transferRejectionMessage) = await TryEnqueueDownloadAsync(connection.Username, connection.IPEndPoint, transferRequest.Filename).ConfigureAwait(false); if (transferRejected) { await connection.WriteAsync(new TransferResponse(transferRequest.Token, transferRejectionMessage)).ConfigureAwait(false); await connection.WriteAsync(new UploadDenied(transferRequest.Filename, transferRejectionMessage)).ConfigureAwait(false); } else { await connection.WriteAsync(new TransferResponse(transferRequest.Token, "Queued")).ConfigureAwait(false); await TrySendPlaceInQueueAsync(connection, transferRequest.Filename).ConfigureAwait(false); } } break; case MessageCode.Peer.UploadDenied: var uploadDeniedResponse = UploadDenied.FromByteArray(message); Diagnostic.Debug($"Download of {uploadDeniedResponse.Filename} from {connection.Username} was denied: {uploadDeniedResponse.Message}"); SoulseekClient.Waiter.Throw(new WaitKey(MessageCode.Peer.TransferRequest, connection.Username, uploadDeniedResponse.Filename), new TransferRejectedException(uploadDeniedResponse.Message)); DownloadDenied?.Invoke(this, new DownloadDeniedEventArgs(connection.Username, uploadDeniedResponse.Filename, uploadDeniedResponse.Message)); break; case MessageCode.Peer.PlaceInQueueResponse: var placeInQueueResponse = PlaceInQueueResponse.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(MessageCode.Peer.PlaceInQueueResponse, connection.Username, placeInQueueResponse.Filename), placeInQueueResponse); break; case MessageCode.Peer.PlaceInQueueRequest: var placeInQueueRequest = PlaceInQueueRequest.FromByteArray(message); await TrySendPlaceInQueueAsync(connection, placeInQueueRequest.Filename).ConfigureAwait(false); break; case MessageCode.Peer.UploadFailed: var uploadFailedResponse = UploadFailed.FromByteArray(message); var msg = $"Download of {uploadFailedResponse.Filename} reported as failed by {connection.Username}"; var download = SoulseekClient.DownloadDictionary.Values.FirstOrDefault(d => d.Username == connection.Username && d.Filename == uploadFailedResponse.Filename); if (download != null) { SoulseekClient.Waiter.Throw(new WaitKey(MessageCode.Peer.TransferRequest, download.Username, download.Filename), new TransferException(msg)); } Diagnostic.Debug(msg); DownloadFailed?.Invoke(this, new DownloadFailedEventArgs(connection.Username, uploadFailedResponse.Filename)); break; default: Diagnostic.Debug($"Unhandled peer message: {code} from {connection.Username} ({connection.IPEndPoint}); {message.Length} bytes"); break; } } catch (Exception ex) { Diagnostic.Warning($"Error handling peer message: {code} from {connection.Username} ({connection.IPEndPoint}); {ex.Message}", ex); } }
public async void HandleMessage(object sender, byte[] message) { var connection = (IMessageConnection)sender; var code = new MessageReader <MessageCode.Peer>(message).ReadCode(); Diagnostic.Debug($"Peer message received: {code} from {connection.Username} ({connection.IPAddress}:{connection.Port})"); try { switch (code) { case MessageCode.Peer.SearchResponse: var searchResponse = SearchResponseSlim.Parse(message); if (SoulseekClient.Searches.TryGetValue(searchResponse.Token, out var search)) { search.AddResponse(searchResponse); } break; case MessageCode.Peer.BrowseResponse: var browseWaitKey = new WaitKey(MessageCode.Peer.BrowseResponse, connection.Username); try { SoulseekClient.Waiter.Complete(browseWaitKey, BrowseResponse.Parse(message)); } catch (Exception ex) { SoulseekClient.Waiter.Throw(browseWaitKey, new MessageReadException("The peer returned an invalid browse response.", ex)); throw; } break; case MessageCode.Peer.InfoResponse: var infoResponse = UserInfoResponse.Parse(message); SoulseekClient.Waiter.Complete(new WaitKey(MessageCode.Peer.InfoResponse, connection.Username), infoResponse); break; case MessageCode.Peer.TransferResponse: var transferResponse = TransferResponse.Parse(message); Console.WriteLine($"Got response from {connection.Username}: {transferResponse.Token}"); SoulseekClient.Waiter.Complete(new WaitKey(MessageCode.Peer.TransferResponse, connection.Username, transferResponse.Token), transferResponse); break; case MessageCode.Peer.QueueDownload: // the end state here is to wait until there's actually a free slot, then send this request to the peer to // let them know we are ready to start the actual transfer. var queueDownloadRequest = QueueDownloadRequest.Parse(message); var(queueAllowed, queueRejectionMessage) = SoulseekClient.Resolvers.QueueDownloadResponse(connection.Username, connection.IPAddress, connection.Port, queueDownloadRequest.Filename); if (!queueAllowed) { await connection.WriteAsync(new QueueFailedResponse(queueDownloadRequest.Filename, queueRejectionMessage)).ConfigureAwait(false); } break; case MessageCode.Peer.TransferRequest: var transferRequest = TransferRequest.Parse(message); if (transferRequest.Direction == TransferDirection.Upload) { SoulseekClient.Waiter.Complete(new WaitKey(MessageCode.Peer.TransferRequest, connection.Username, transferRequest.Filename), transferRequest); } else { var(transferAllowed, transferRejectionMessage) = SoulseekClient.Resolvers.QueueDownloadResponse(connection.Username, connection.IPAddress, connection.Port, transferRequest.Filename); if (!transferAllowed) { await connection.WriteAsync(new TransferResponse(transferRequest.Token, transferRejectionMessage)).ConfigureAwait(false); await connection.WriteAsync(new QueueFailedResponse(transferRequest.Filename, transferRejectionMessage)).ConfigureAwait(false); } else { await connection.WriteAsync(new TransferResponse(transferRequest.Token, "Queued.")).ConfigureAwait(false); } } break; case MessageCode.Peer.QueueFailed: var queueFailedResponse = QueueFailedResponse.Parse(message); SoulseekClient.Waiter.Throw(new WaitKey(MessageCode.Peer.TransferRequest, connection.Username, queueFailedResponse.Filename), new TransferRejectedException(queueFailedResponse.Message)); break; case MessageCode.Peer.PlaceInQueueResponse: var placeInQueueResponse = PeerPlaceInQueueResponse.Parse(message); SoulseekClient.Waiter.Complete(new WaitKey(MessageCode.Peer.PlaceInQueueResponse, connection.Username, placeInQueueResponse.Filename), placeInQueueResponse); break; case MessageCode.Peer.UploadFailed: var uploadFailedResponse = PeerUploadFailedResponse.Parse(message); var msg = $"Download of {uploadFailedResponse.Filename} reported as failed by {connection.Username}."; var download = SoulseekClient.Downloads.Values.FirstOrDefault(d => d.Username == connection.Username && d.Filename == uploadFailedResponse.Filename); if (download != null) { SoulseekClient.Waiter.Throw(new WaitKey(MessageCode.Peer.TransferRequest, download.Username, download.Filename), new TransferException(msg)); SoulseekClient.Waiter.Throw(download.WaitKey, new TransferException(msg)); } Diagnostic.Debug(msg); break; case MessageCode.Peer.BrowseRequest: var browseResponse = SoulseekClient.Resolvers.BrowseResponse(connection.Username, connection.IPAddress, connection.Port); await connection.WriteAsync(browseResponse).ConfigureAwait(false); break; default: Diagnostic.Debug($"Unhandled peer message: {code} from {connection.Username} ({connection.IPAddress}:{connection.Port}); {message.Length} bytes"); break; } } catch (Exception ex) { Diagnostic.Warning($"Error handling peer message: {code} from {connection.Username} ({connection.IPAddress}:{connection.Port}); {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 connection = (IMessageConnection)sender; var code = new MessageReader <MessageCode.Peer>(message).ReadCode(); Diagnostic.Debug($"Peer message received: {code} from {connection.Username} ({connection.IPAddress}:{connection.Port})"); try { switch (code) { case MessageCode.Peer.SearchResponse: var searchResponse = SearchResponseSlim.FromByteArray(message); if (SoulseekClient.Searches.TryGetValue(searchResponse.Token, out var search)) { search.AddResponse(searchResponse); } break; case MessageCode.Peer.BrowseResponse: var browseWaitKey = new WaitKey(MessageCode.Peer.BrowseResponse, connection.Username); try { SoulseekClient.Waiter.Complete(browseWaitKey, BrowseResponse.FromByteArray(message)); } catch (Exception ex) { SoulseekClient.Waiter.Throw(browseWaitKey, new MessageReadException("The peer returned an invalid browse response.", ex)); throw; } break; case MessageCode.Peer.InfoRequest: var outgoingInfo = await new ClientOptions() .UserInfoResponseResolver(connection.Username, connection.IPAddress, connection.Port).ConfigureAwait(false); try { outgoingInfo = await SoulseekClient.Options .UserInfoResponseResolver(connection.Username, connection.IPAddress, connection.Port).ConfigureAwait(false); } catch (Exception ex) { Diagnostic.Warning($"Failed to resolve UserInfoResponse: {ex.Message}", ex); } await connection.WriteAsync(outgoingInfo.ToByteArray()).ConfigureAwait(false); break; case MessageCode.Peer.BrowseRequest: var browseResponse = await new ClientOptions() .BrowseResponseResolver(connection.Username, connection.IPAddress, connection.Port).ConfigureAwait(false); try { browseResponse = await SoulseekClient.Options.BrowseResponseResolver(connection.Username, connection.IPAddress, connection.Port).ConfigureAwait(false); } catch (Exception ex) { Diagnostic.Warning($"Failed to resolve BrowseResponse: {ex.Message}", ex); } await connection.WriteAsync(browseResponse.ToByteArray()).ConfigureAwait(false); break; case MessageCode.Peer.InfoResponse: var incomingInfo = UserInfoResponse.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(MessageCode.Peer.InfoResponse, connection.Username), incomingInfo); break; case MessageCode.Peer.TransferResponse: var transferResponse = TransferResponse.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(MessageCode.Peer.TransferResponse, connection.Username, transferResponse.Token), transferResponse); break; case MessageCode.Peer.QueueDownload: var queueDownloadRequest = QueueDownloadRequest.FromByteArray(message); var(queueRejected, queueRejectionMessage) = await TryEnqueueDownloadAsync(connection.Username, connection.IPAddress, connection.Port, queueDownloadRequest.Filename).ConfigureAwait(false); if (queueRejected) { await connection.WriteAsync(new QueueFailedResponse(queueDownloadRequest.Filename, queueRejectionMessage).ToByteArray()).ConfigureAwait(false); } break; case MessageCode.Peer.TransferRequest: var transferRequest = TransferRequest.FromByteArray(message); if (transferRequest.Direction == TransferDirection.Upload) { SoulseekClient.Waiter.Complete(new WaitKey(MessageCode.Peer.TransferRequest, connection.Username, transferRequest.Filename), transferRequest); } else { var(transferRejected, transferRejectionMessage) = await TryEnqueueDownloadAsync(connection.Username, connection.IPAddress, connection.Port, transferRequest.Filename).ConfigureAwait(false); if (transferRejected) { await connection.WriteAsync(new TransferResponse(transferRequest.Token, transferRejectionMessage).ToByteArray()).ConfigureAwait(false); await connection.WriteAsync(new QueueFailedResponse(transferRequest.Filename, transferRejectionMessage).ToByteArray()).ConfigureAwait(false); } else { await connection.WriteAsync(new TransferResponse(transferRequest.Token, "Queued.").ToByteArray()).ConfigureAwait(false); } } break; case MessageCode.Peer.QueueFailed: var queueFailedResponse = QueueFailedResponse.FromByteArray(message); SoulseekClient.Waiter.Throw(new WaitKey(MessageCode.Peer.TransferRequest, connection.Username, queueFailedResponse.Filename), new TransferRejectedException(queueFailedResponse.Message)); break; case MessageCode.Peer.PlaceInQueueResponse: var placeInQueueResponse = PlaceInQueueResponse.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(MessageCode.Peer.PlaceInQueueResponse, connection.Username, placeInQueueResponse.Filename), placeInQueueResponse); break; case MessageCode.Peer.UploadFailed: var uploadFailedResponse = UploadFailed.FromByteArray(message); var msg = $"Download of {uploadFailedResponse.Filename} reported as failed by {connection.Username}."; var download = SoulseekClient.Downloads.Values.FirstOrDefault(d => d.Username == connection.Username && d.Filename == uploadFailedResponse.Filename); if (download != null) { SoulseekClient.Waiter.Throw(new WaitKey(MessageCode.Peer.TransferRequest, download.Username, download.Filename), new TransferException(msg)); SoulseekClient.Waiter.Throw(download.WaitKey, new TransferException(msg)); } Diagnostic.Debug(msg); break; default: Diagnostic.Debug($"Unhandled peer message: {code} from {connection.Username} ({connection.IPAddress}:{connection.Port}); {message.Length} bytes"); break; } } catch (Exception ex) { Diagnostic.Warning($"Error handling peer message: {code} from {connection.Username} ({connection.IPAddress}:{connection.Port}); {ex.Message}", ex); } }