public void FromByteArray_Returns_Expected_Data(int level) { var msg = new MessageBuilder() .WriteCode(MessageCode.Distributed.BranchLevel) .WriteInteger(level) .Build(); var response = DistributedBranchLevel.FromByteArray(msg); Assert.Equal(level, response.Level); }
public void FromByteArray_Throws_MessageException_On_Code_Mismatch() { var msg = new MessageBuilder() .WriteCode(MessageCode.Distributed.ChildDepth) .WriteInteger(1) .Build(); var ex = Record.Exception(() => DistributedBranchLevel.FromByteArray(msg)); Assert.NotNull(ex); Assert.IsType <MessageException>(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 connection = (IMessageConnection)sender; var code = new MessageReader <MessageCode.Distributed>(message).ReadCode(); if (code != MessageCode.Distributed.SearchRequest && code != MessageCode.Distributed.EmbeddedMessage) { Diagnostic.Debug($"Distributed message received: {code} from {connection.Username} ({connection.IPEndPoint}) (id: {connection.Id})"); } else if (SoulseekClient.Options.DeduplicateSearchRequests) { var current = Convert.ToBase64String(message); if (DeduplicationHash == current) { return; } DeduplicationHash = current; } try { switch (code) { // if we are connected to a branch root, we will receive EmbeddedMessage/93. case MessageCode.Distributed.EmbeddedMessage: var embeddedMessage = EmbeddedMessage.FromByteArray(message); switch (embeddedMessage.DistributedCode) { // convert this message to a normal DistributedSearchRequest before forwarding. this functionality is based // on the observation that branch roots send embedded messages to children, while parents that are not a branch root // send a plain SearchRequest/3. case MessageCode.Distributed.SearchRequest: var embeddedSearchRequest = DistributedSearchRequest.FromByteArray(embeddedMessage.DistributedMessage); _ = SoulseekClient.DistributedConnectionManager.BroadcastMessageAsync(embeddedMessage.DistributedMessage).ConfigureAwait(false); await SoulseekClient.SearchResponder.TryRespondAsync(embeddedSearchRequest.Username, embeddedSearchRequest.Token, embeddedSearchRequest.Query).ConfigureAwait(false); break; default: Diagnostic.Debug($"Unhandled embedded message: {code} from {connection.Username} ({connection.IPEndPoint}); {message.Length} bytes"); break; } break; // if we are connected to anyone other than a branch root, we will receive SearchRequest/3. case MessageCode.Distributed.SearchRequest: var searchRequest = DistributedSearchRequest.FromByteArray(message); _ = SoulseekClient.DistributedConnectionManager.BroadcastMessageAsync(message).ConfigureAwait(false); await SoulseekClient.SearchResponder.TryRespondAsync(searchRequest.Username, searchRequest.Token, searchRequest.Query).ConfigureAwait(false); break; case MessageCode.Distributed.Ping: var pingResponse = DistributedPingResponse.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(MessageCode.Distributed.Ping, connection.Username), pingResponse); break; case MessageCode.Distributed.BranchLevel: var branchLevel = DistributedBranchLevel.FromByteArray(message); if ((connection.Username, connection.IPEndPoint) == SoulseekClient.DistributedConnectionManager.Parent) { SoulseekClient.DistributedConnectionManager.SetParentBranchLevel(branchLevel.Level); } break; case MessageCode.Distributed.BranchRoot: var branchRoot = DistributedBranchRoot.FromByteArray(message); if ((connection.Username, connection.IPEndPoint) == SoulseekClient.DistributedConnectionManager.Parent) { SoulseekClient.DistributedConnectionManager.SetParentBranchRoot(branchRoot.Username); } break; case MessageCode.Distributed.ChildDepth: var childDepth = DistributedChildDepth.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(Constants.WaitKey.ChildDepthMessage, connection.Key), childDepth.Depth); break; default: Diagnostic.Debug($"Unhandled distributed message: {code} from {connection.Username} ({connection.IPEndPoint}); {message.Length} bytes"); break; } } catch (Exception ex) { Diagnostic.Warning($"Error handling distributed message: {code} from {connection.Username} ({connection.IPEndPoint}); {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.Distributed>(message).ReadCode(); if (code != MessageCode.Distributed.SearchRequest) { Diagnostic.Debug($"Distributed message received: {code} from {connection.Username} ({connection.IPAddress}:{connection.Port})"); } try { switch (code) { // some clients erroneously send code 93, which is a server code, in place of 3. case MessageCode.Distributed.ServerSearchRequest: case MessageCode.Distributed.SearchRequest: var searchRequest = DistributedSearchRequest.FromByteArray(message); SearchResponse searchResponse; SoulseekClient.Waiter.Complete(new WaitKey(Constants.WaitKey.SearchRequestMessage, connection.Context, connection.Key)); SoulseekClient.DistributedConnectionManager.BroadcastMessageAsync(message).Forget(); if (SoulseekClient.Options.SearchResponseResolver == default) { break; } try { searchResponse = await SoulseekClient.Options.SearchResponseResolver(searchRequest.Username, searchRequest.Token, searchRequest.Query).ConfigureAwait(false); if (searchResponse != null && searchResponse.FileCount > 0) { var(ip, port) = await SoulseekClient.GetUserAddressAsync(searchRequest.Username).ConfigureAwait(false); var peerConnection = await SoulseekClient.PeerConnectionManager.GetOrAddMessageConnectionAsync(searchRequest.Username, ip, port, 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; case MessageCode.Distributed.Ping: Diagnostic.Debug($"PING?"); var pingResponse = new PingResponse(SoulseekClient.GetNextToken()); await connection.WriteAsync(pingResponse.ToByteArray()).ConfigureAwait(false); Diagnostic.Debug($"PONG!"); break; case MessageCode.Distributed.BranchLevel: var branchLevel = DistributedBranchLevel.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(Constants.WaitKey.BranchLevelMessage, connection.Context, connection.Key), branchLevel.Level); if ((connection.Username, connection.IPAddress, connection.Port) == SoulseekClient.DistributedConnectionManager.Parent) { SoulseekClient.DistributedConnectionManager.SetBranchLevel(branchLevel.Level); } break; case MessageCode.Distributed.BranchRoot: var branchRoot = DistributedBranchRoot.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(Constants.WaitKey.BranchRootMessage, connection.Context, connection.Key), branchRoot.Username); if ((connection.Username, connection.IPAddress, connection.Port) == SoulseekClient.DistributedConnectionManager.Parent) { SoulseekClient.DistributedConnectionManager.SetBranchRoot(branchRoot.Username); } break; case MessageCode.Distributed.ChildDepth: var childDepth = DistributedChildDepth.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(Constants.WaitKey.ChildDepthMessage, connection.Key), childDepth.Depth); break; default: Diagnostic.Debug($"Unhandled distributed message: {code} from {connection.Username} ({connection.IPAddress}:{connection.Port}); {message.Length} bytes"); break; } } catch (Exception ex) { Diagnostic.Warning($"Error handling distributed 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 HandleMessageRead(object sender, byte[] message) { var connection = (IMessageConnection)sender; var code = new MessageReader <MessageCode.Distributed>(message).ReadCode(); if (code != MessageCode.Distributed.SearchRequest && code != MessageCode.Distributed.ServerSearchRequest) { Diagnostic.Debug($"Distributed message received: {code} from {connection.Username} ({connection.IPEndPoint}) (id: {connection.Id})"); } else if (SoulseekClient.Options.DeduplicateSearchRequests) { var current = Convert.ToBase64String(message); if (DeduplicationHash == current) { return; } DeduplicationHash = current; } try { switch (code) { // if we are connected to a branch root, we get search requests with code DistributedServerSearchRequest. // convert this message to a normal DistributedSearchRequest before forwarding. not sure if this is correct, // but it would match the observed behavior. these messages may also be forwarded from the server message // handler if we haven't connected to a distributed parent in a timely manner. case MessageCode.Distributed.ServerSearchRequest: var serverSearchRequest = DistributedServerSearchRequest.FromByteArray(message); var forwardedSearchRequest = new DistributedSearchRequest(serverSearchRequest.Username, serverSearchRequest.Token, serverSearchRequest.Query); _ = SoulseekClient.DistributedConnectionManager.BroadcastMessageAsync(forwardedSearchRequest.ToByteArray()).ConfigureAwait(false); await TrySendSearchResults(serverSearchRequest.Username, serverSearchRequest.Token, serverSearchRequest.Query).ConfigureAwait(false); break; // if we are connected to anyone other than a branch root, we should get search requests with code // SearchRequest. forward these requests as is. case MessageCode.Distributed.SearchRequest: var searchRequest = DistributedSearchRequest.FromByteArray(message); _ = SoulseekClient.DistributedConnectionManager.BroadcastMessageAsync(searchRequest.ToByteArray()).ConfigureAwait(false); await TrySendSearchResults(searchRequest.Username, searchRequest.Token, searchRequest.Query).ConfigureAwait(false); break; case MessageCode.Distributed.Ping: var pingResponse = DistributedPingResponse.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(MessageCode.Distributed.Ping, connection.Username), pingResponse); break; case MessageCode.Distributed.BranchLevel: var branchLevel = DistributedBranchLevel.FromByteArray(message); if ((connection.Username, connection.IPEndPoint) == SoulseekClient.DistributedConnectionManager.Parent) { SoulseekClient.DistributedConnectionManager.SetBranchLevel(branchLevel.Level); } break; case MessageCode.Distributed.BranchRoot: var branchRoot = DistributedBranchRoot.FromByteArray(message); if ((connection.Username, connection.IPEndPoint) == SoulseekClient.DistributedConnectionManager.Parent) { SoulseekClient.DistributedConnectionManager.SetBranchRoot(branchRoot.Username); } break; case MessageCode.Distributed.ChildDepth: var childDepth = DistributedChildDepth.FromByteArray(message); SoulseekClient.Waiter.Complete(new WaitKey(Constants.WaitKey.ChildDepthMessage, connection.Key), childDepth.Depth); break; default: Diagnostic.Debug($"Unhandled distributed message: {code} from {connection.Username} ({connection.IPEndPoint}); {message.Length} bytes"); break; } } catch (Exception ex) { Diagnostic.Warning($"Error handling distributed message: {code} from {connection.Username} ({connection.IPEndPoint}); {ex.Message}", ex); } }