public void Parse_Handles_Empty_Directory() { var name = Guid.NewGuid().ToString(); var msg = new MessageBuilder() .WriteCode(MessageCode.Peer.BrowseResponse) .WriteInteger(1) // directory count .WriteString(name) // first directory name .WriteInteger(0) // first directory file count .Compress() .Build(); BrowseResponse r = default; var ex = Record.Exception(() => r = BrowseResponse.FromByteArray(msg)); Assert.Null(ex); Assert.Equal(1, r.DirectoryCount); Assert.Single(r.Directories); var d = r.Directories.ToList(); Assert.Equal(name, d[0].Directoryname); Assert.Equal(0, d[0].FileCount); Assert.Empty(d[0].Files); }
public void Parse_Handles_A_Complete_Response() { var dirs = new List <Directory>(); for (int i = 0; i < 5; i++) { dirs.Add(GetRandomDirectory(i)); } var builder = new MessageBuilder() .WriteCode(MessageCode.Peer.BrowseResponse) .WriteInteger(dirs.Count); foreach (var dir in dirs) { BuildDirectory(builder, dir); } var msg = builder .Compress() .Build(); BrowseResponse r = default; var ex = Record.Exception(() => r = BrowseResponse.FromByteArray(msg)); Assert.Null(ex); Assert.Equal(dirs.Count, r.DirectoryCount); Assert.Equal(dirs.Count, r.Directories.Count); var msgDirs = r.Directories.ToList(); for (int i = 0; i < msgDirs.Count; i++) { Assert.Equal(dirs[i].Directoryname, msgDirs[i].Directoryname); Assert.Equal(dirs[i].FileCount, msgDirs[i].FileCount); var files = dirs[i].Files.ToList(); var msgFiles = msgDirs[i].Files.ToList(); for (int j = 0; j < msgDirs[i].FileCount; j++) { Assert.Equal(files[j].Code, msgFiles[j].Code); Assert.Equal(files[j].Filename, msgFiles[j].Filename); Assert.Equal(files[j].Size, msgFiles[j].Size); Assert.Equal(files[j].Extension, msgFiles[j].Extension); Assert.Equal(files[j].AttributeCount, msgFiles[j].AttributeCount); var attr = files[j].Attributes.ToList(); var msgAttr = files[j].Attributes.ToList(); for (int k = 0; k < msgFiles[j].AttributeCount; k++) { Assert.Equal(attr[k].Type, msgAttr[k].Type); Assert.Equal(attr[k].Value, msgAttr[k].Value); } } } }
public void Parse_Throws_MessageException_On_Code_Mismatch() { var msg = new MessageBuilder() .WriteCode(MessageCode.Peer.TransferResponse) .Build(); var ex = Record.Exception(() => BrowseResponse.FromByteArray(msg)); Assert.NotNull(ex); Assert.IsType <MessageException>(ex); }
public void Parse_Throws_MessageCompressionException_On_Uncompressed_Payload() { var msg = new MessageBuilder() .WriteCode(MessageCode.Peer.BrowseResponse) .WriteBytes(new byte[] { 0x0, 0x1, 0x2, 0x3 }) .Build(); var ex = Record.Exception(() => BrowseResponse.FromByteArray(msg)); Assert.NotNull(ex); Assert.IsType <MessageCompressionException>(ex); Assert.IsType <ZStreamException>(ex.InnerException); }
public void Parse_Returns_Empty_Response_Given_Empty_Message() { var msg = new MessageBuilder() .WriteCode(MessageCode.Peer.BrowseResponse) .WriteInteger(0) .Compress() .Build(); BrowseResponse r = default; var ex = Record.Exception(() => r = BrowseResponse.FromByteArray(msg)); Assert.Null(ex); Assert.Equal(0, r.DirectoryCount); Assert.Empty(r.Directories); }
public void Parse_Throws_MessageReadException_On_Missing_Data() { var name = Guid.NewGuid().ToString(); var msg = new MessageBuilder() .WriteCode(MessageCode.Peer.BrowseResponse) .WriteInteger(1) // directory count .WriteString(name) // first directory name .Compress() // count is missing .Build(); BrowseResponse r = default; var ex = Record.Exception(() => r = BrowseResponse.FromByteArray(msg)); Assert.NotNull(ex); Assert.IsType <MessageReadException>(ex); }
public void Parse_Handles_Files_With_No_Attributes() { var name = Guid.NewGuid().ToString(); var msg = new MessageBuilder() .WriteCode(MessageCode.Peer.BrowseResponse) .WriteInteger(1) // directory count .WriteString(name) // first directory name .WriteInteger(1) // first directory file count .WriteByte(0x0) // file code .WriteString("foo") // name .WriteLong(12) // size .WriteString("bar") // extension .WriteInteger(0) // attribute count .Compress() .Build(); BrowseResponse r = default; var ex = Record.Exception(() => r = BrowseResponse.FromByteArray(msg)); Assert.Null(ex); Assert.Equal(1, r.DirectoryCount); Assert.Single(r.Directories); var d = r.Directories.ToList(); Assert.Equal(name, d[0].Directoryname); Assert.Equal(1, d[0].FileCount); Assert.Single(d[0].Files); var f = d[0].Files.ToList(); Assert.Equal(0x0, f[0].Code); Assert.Equal("foo", f[0].Filename); Assert.Equal(12, f[0].Size); Assert.Equal("bar", f[0].Extension); Assert.Equal(0, f[0].AttributeCount); Assert.Empty(f[0].Attributes); }
/// <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 = SearchResponseSlim.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, 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: UserInfo outgoingInfo; try { outgoingInfo = await SoulseekClient.Options .UserInfoResponseResolver(connection.Username, connection.IPEndPoint).ConfigureAwait(false); } catch (Exception ex) { outgoingInfo = await new SoulseekClientOptions() .UserInfoResponseResolver(connection.Username, connection.IPEndPoint).ConfigureAwait(false); Diagnostic.Warning($"Failed to resolve UserInfoResponse: {ex.Message}", ex); } await connection.WriteAsync(outgoingInfo.ToByteArray()).ConfigureAwait(false); break; case MessageCode.Peer.BrowseRequest: IEnumerable <Directory> 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 BrowseResponse: {ex.Message}", ex); } var browseResponseMessage = new BrowseResponse(browseResponse.Count(), browseResponse); await connection.WriteAsync(browseResponseMessage.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 = EnqueueDownloadRequest.FromByteArray(message); var(queueRejected, queueRejectionMessage) = await TryEnqueueDownloadAsync(connection.Username, connection.IPEndPoint, queueDownloadRequest.Filename).ConfigureAwait(false); if (queueRejected) { await connection.WriteAsync(new EnqueueFailedResponse(queueDownloadRequest.Filename, queueRejectionMessage).ToByteArray()).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.Downloads.IsEmpty && SoulseekClient.Downloads.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. await connection.WriteAsync(new TransferResponse(transferRequest.Token, string.Empty).ToByteArray()).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).ToByteArray()).ConfigureAwait(false); await connection.WriteAsync(new EnqueueFailedResponse(transferRequest.Filename, transferRejectionMessage).ToByteArray()).ConfigureAwait(false); } else { await connection.WriteAsync(new TransferResponse(transferRequest.Token, "Queued.").ToByteArray()).ConfigureAwait(false); await TrySendPlaceInQueueAsync(connection, transferRequest.Filename).ConfigureAwait(false); } } break; case MessageCode.Peer.QueueFailed: var queueFailedResponse = EnqueueFailedResponse.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.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.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.IPEndPoint}); {message.Length} bytes"); break; } } catch (Exception ex) { Diagnostic.Warning($"Error handling peer message: {code} from {connection.Username} ({connection.IPEndPoint}); {ex.Message}", ex); } }