internal async void ReceiveMessagesAsync(IConnection connection, IEncryption decryptor, RateLimiterGroup downloadLimiter, ConnectionMonitor monitor, TorrentManager torrentManager, PeerId id) { await MainLoop.SwitchToThreadpool(); ByteBufferPool.Releaser releaser = default; try { while (true) { if (id.AmRequestingPiecesCount == 0 && releaser.Buffer != null) { releaser.Dispose(); releaser = NetworkIO.BufferPool.Rent(1, out ByteBuffer _); } else if (id.AmRequestingPiecesCount > 0 && releaser.Buffer == null) { releaser.Dispose(); releaser = NetworkIO.BufferPool.Rent(Piece.BlockSize, out ByteBuffer _); } PeerMessage message = await PeerIO.ReceiveMessageAsync(connection, decryptor, downloadLimiter, monitor, torrentManager.Monitor, torrentManager, releaser.Buffer).ConfigureAwait(false); HandleReceivedMessage(id, torrentManager, message); } } catch { releaser.Dispose(); await ClientEngine.MainLoop; CleanupSocket(torrentManager, id); } }
public static async ReusableTask ReceiveAsync(IConnection connection, ByteBuffer buffer, int offset, int count, IRateLimiter rateLimiter, SpeedMonitor peerMonitor, SpeedMonitor managerMonitor) { await MainLoop.SwitchToThreadpool(); while (count > 0) { int transferred; bool unlimited = rateLimiter?.Unlimited ?? true; int shouldRead = unlimited ? count : Math.Min(ChunkLength, count); if (rateLimiter != null && !unlimited && !rateLimiter.TryProcess(shouldRead)) { var tcs = new ReusableTaskCompletionSource <int> (); lock (receiveQueue) receiveQueue.Enqueue(new QueuedIO(connection, buffer, offset, shouldRead, rateLimiter, tcs)); transferred = await tcs.Task.ConfigureAwait(false); } else { transferred = await connection.ReceiveAsync(buffer, offset, shouldRead).ConfigureAwait(false); } if (transferred == 0) { throw new ConnectionClosedException("Socket receive returned 0, indicating the connection has been closed."); } peerMonitor?.AddDelta(transferred); managerMonitor?.AddDelta(transferred); offset += transferred; count -= transferred; } }
public static async ReusableTask <(PeerMessage message, PeerMessage.Releaser releaser)> ReceiveMessageAsync(IPeerConnection connection, IEncryption decryptor, IRateLimiter?rateLimiter, ConnectionMonitor?peerMonitor, ConnectionMonitor?managerMonitor, ITorrentManagerInfo?torrentData, Memory <byte> buffer) { await MainLoop.SwitchToThreadpool(); int messageHeaderLength = 4; int messageBodyLength; Memory <byte> messageHeaderBuffer = buffer; Memory <byte> messageBuffer = buffer; ByteBufferPool.Releaser messageHeaderReleaser = default; ByteBufferPool.Releaser messageBufferReleaser = default; if (messageHeaderBuffer.IsEmpty) { messageHeaderReleaser = NetworkIO.BufferPool.Rent(messageHeaderLength, out messageHeaderBuffer); } using (messageHeaderReleaser) { await NetworkIO.ReceiveAsync(connection, messageHeaderBuffer.Slice(0, messageHeaderLength), rateLimiter, peerMonitor?.ProtocolDown, managerMonitor?.ProtocolDown).ConfigureAwait(false); decryptor.Decrypt(messageHeaderBuffer.Span.Slice(0, messageHeaderLength)); messageBodyLength = Message.ReadInt(messageHeaderBuffer.Span); if (messageBodyLength < 0 || messageBodyLength > MaxMessageLength) { connection.Dispose(); throw new ProtocolException($"Invalid message length received. Value was '{messageBodyLength}'"); } if (messageBodyLength == 0) { return(KeepAliveMessage.Instance, default);
public static async ReusableTask SendAsync(IPeerConnection connection, SocketMemory buffer, IRateLimiter rateLimiter, SpeedMonitor peerMonitor, SpeedMonitor managerMonitor) { await MainLoop.SwitchToThreadpool(); while (buffer.Length > 0) { int transferred; bool unlimited = rateLimiter?.Unlimited ?? true; int shouldRead = unlimited ? buffer.Length : Math.Min(ChunkLength, buffer.Length); if (rateLimiter != null && !unlimited && !rateLimiter.TryProcess(shouldRead)) { var tcs = new ReusableTaskCompletionSource <int> (); lock (sendQueue) sendQueue.Enqueue(new QueuedIO(connection, buffer.Slice(0, shouldRead), rateLimiter, tcs)); transferred = await tcs.Task.ConfigureAwait(false); } else { transferred = await connection.SendAsync(buffer.Slice(0, shouldRead)).ConfigureAwait(false); } if (transferred == 0) { throw new ConnectionClosedException("Socket send returned 0, indicating the connection has been closed."); } peerMonitor?.AddDelta(transferred); managerMonitor?.AddDelta(transferred); buffer = buffer.Slice(transferred); } }
/// <summary> /// /// </summary> /// <param name="manager">The torrent which the peer is associated with.</param> /// <param name="id">The peer whose message queue you want to start processing</param> internal async void TryProcessQueue(TorrentManager manager, PeerId id) { if (!id.MessageQueue.BeginProcessing()) { return; } await MainLoop.SwitchToThreadpool(); ByteBufferPool.Releaser socketMemoryReleaser = default; Memory <byte> socketMemory = default; try { while (id.MessageQueue.TryDequeue(out PeerMessage? msg, out PeerMessage.Releaser msgReleaser)) { using var autorelease = msgReleaser; if (socketMemory.IsEmpty || socketMemory.Length < msg.ByteLength) { socketMemoryReleaser.Dispose(); socketMemoryReleaser = NetworkIO.BufferPool.Rent(msg.ByteLength, out socketMemory); } var buffer = socketMemory.Slice(0, msg.ByteLength); if (msg is PieceMessage pm) { pm.SetData((default, buffer.Slice(buffer.Length - pm.RequestLength)));
internal async void ReceiveMessagesAsync(IPeerConnection connection, IEncryption decryptor, RateLimiterGroup downloadLimiter, ConnectionMonitor monitor, TorrentManager torrentManager, PeerId id) { await MainLoop.SwitchToThreadpool(); Memory <byte> currentBuffer = default; Memory <byte> smallBuffer = default; ByteBufferPool.Releaser smallReleaser = default; Memory <byte> largeBuffer = default; ByteBufferPool.Releaser largeReleaser = default; try { while (true) { if (id.AmRequestingPiecesCount == 0) { if (!largeBuffer.IsEmpty) { largeReleaser.Dispose(); largeReleaser = default; largeBuffer = currentBuffer = default; } if (smallBuffer.IsEmpty) { smallReleaser = NetworkIO.BufferPool.Rent(ByteBufferPool.SmallMessageBufferSize, out smallBuffer); currentBuffer = smallBuffer; } } else { if (!smallBuffer.IsEmpty) { smallReleaser.Dispose(); smallReleaser = default; smallBuffer = currentBuffer = default; } if (largeBuffer.IsEmpty) { largeReleaser = NetworkIO.BufferPool.Rent(ByteBufferPool.LargeMessageBufferSize, out largeBuffer); currentBuffer = largeBuffer; } } (PeerMessage message, PeerMessage.Releaser releaser) = await PeerIO.ReceiveMessageAsync(connection, decryptor, downloadLimiter, monitor, torrentManager.Monitor, torrentManager, currentBuffer).ConfigureAwait(false); HandleReceivedMessage(id, torrentManager, message, releaser); } } catch { await ClientEngine.MainLoop; CleanupSocket(torrentManager, id); } finally { smallReleaser.Dispose(); largeReleaser.Dispose(); } }
public static async ReusableTask <PeerMessage> ReceiveMessageAsync(IConnection connection, IEncryption decryptor, IRateLimiter rateLimiter, ConnectionMonitor peerMonitor, ConnectionMonitor managerMonitor, ITorrentData torrentData, ByteBuffer buffer) { await MainLoop.SwitchToThreadpool(); int messageHeaderLength = 4; int messageBodyLength; ByteBuffer messageHeaderBuffer = buffer; ByteBuffer messageBuffer = buffer; ByteBufferPool.Releaser messageBufferReleaser = default; using (var headerReleaser = buffer == null ? NetworkIO.BufferPool.Rent(messageHeaderLength, out messageHeaderBuffer) : default) { await NetworkIO.ReceiveAsync(connection, messageHeaderBuffer, 0, messageHeaderLength, rateLimiter, peerMonitor?.ProtocolDown, managerMonitor?.ProtocolDown).ConfigureAwait(false); decryptor.Decrypt(messageHeaderBuffer.Data, 0, messageHeaderLength); messageBodyLength = IPAddress.HostToNetworkOrder(BitConverter.ToInt32(messageHeaderBuffer.Data, 0)); if (messageBodyLength < 0 || messageBodyLength > MaxMessageLength) { connection.Dispose(); throw new ProtocolException($"Invalid message length received. Value was '{messageBodyLength}'"); } if (messageBodyLength == 0) { return(new KeepAliveMessage()); } if (buffer == null || buffer.Data.Length < messageBodyLength + messageHeaderLength) { messageBufferReleaser = NetworkIO.BufferPool.Rent(messageBodyLength + messageHeaderLength, out messageBuffer); Buffer.BlockCopy(messageHeaderBuffer.Data, 0, messageBuffer.Data, 0, messageHeaderLength); } } using (messageBufferReleaser) { // Always assume protocol first, then convert to data when we what message it is! await NetworkIO.ReceiveAsync(connection, messageBuffer, messageHeaderLength, messageBodyLength, rateLimiter, peerMonitor?.ProtocolDown, managerMonitor?.ProtocolDown).ConfigureAwait(false); decryptor.Decrypt(messageBuffer.Data, messageHeaderLength, messageBodyLength); // FIXME: manager should never be null, except some of the unit tests do that. var data = PeerMessage.DecodeMessage(messageBuffer.Data, 0, messageHeaderLength + messageBodyLength, torrentData); if (data is PieceMessage msg) { peerMonitor?.ProtocolDown.AddDelta(-msg.RequestLength); managerMonitor?.ProtocolDown.AddDelta(-msg.RequestLength); peerMonitor?.DataDown.AddDelta(msg.RequestLength); managerMonitor?.DataDown.AddDelta(msg.RequestLength); } return(data); } }
public static async ReusableTask <HandshakeMessage> ReceiveHandshakeAsync(IPeerConnection connection, IEncryption decryptor) { await MainLoop.SwitchToThreadpool(); using (NetworkIO.BufferPool.Rent(HandshakeMessage.HandshakeLength, out Memory <byte> buffer)) { await NetworkIO.ReceiveAsync(connection, buffer, null, null, null).ConfigureAwait(false); decryptor.Decrypt(buffer.Span); return(new HandshakeMessage(buffer.Span)); } }
public static async ReusableTask <HandshakeMessage> ReceiveHandshakeAsync(IConnection connection, IEncryption decryptor) { await MainLoop.SwitchToThreadpool(); using (NetworkIO.BufferPool.Rent(HandshakeMessage.HandshakeLength, out ByteBuffer buffer)) { await NetworkIO.ReceiveAsync(connection, buffer, 0, HandshakeMessage.HandshakeLength, null, null, null).ConfigureAwait(false); decryptor.Decrypt(buffer.Data, 0, HandshakeMessage.HandshakeLength); var message = new HandshakeMessage(); message.Decode(buffer.Data, 0, HandshakeMessage.HandshakeLength); return(message); } }
async void ProcessQueue() { // Ensure this doesn't run on the UI thread as the networking calls can do some (partially) blocking operations. // Specifically 'NetworkInterface.GetAllNetworkInterfaces' is synchronous and can take hundreds of milliseconds. await MainLoop.SwitchToThreadpool(); await RateLimiterTask; using var sendingClient = new UdpClient(); var nics = NetworkInterface.GetAllNetworkInterfaces(); while (true) { InfoHash infoHash = null; lock (PendingAnnounces) { if (PendingAnnounces.Count == 0) { // Enforce a minimum delay before the next announce to avoid killing CPU by iterating network interfaces. RateLimiterTask = Task.Delay(1000); ProcessingAnnounces = false; break; } infoHash = PendingAnnounces.Dequeue(); } string message = string.Format(BaseSearchString, ListenPort, infoHash.ToHex()); byte[] data = Encoding.ASCII.GetBytes(message); foreach (var nic in nics) { try { sendingClient.Client.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastInterface, IPAddress.HostToNetworkOrder(nic.GetIPProperties().GetIPv4Properties().Index)); await sendingClient.SendAsync(data, data.Length, MulticastAddressV4).ConfigureAwait(false); } catch { // If data can't be sent, just ignore the error } } } }
public static async ReusableTask SendMessageAsync(IConnection connection, IEncryption encryptor, PeerMessage message, IRateLimiter rateLimiter, ConnectionMonitor peerMonitor, ConnectionMonitor managerMonitor, ByteBuffer buffer = null) { await MainLoop.SwitchToThreadpool(); int count = message.ByteLength; using (buffer == null ? NetworkIO.BufferPool.Rent(count, out buffer) : default) { var pieceMessage = message as PieceMessage; message.Encode(buffer.Data, 0); encryptor.Encrypt(buffer.Data, 0, count); // Assume protocol first, then swap it to data once we successfully send the data bytes. await NetworkIO.SendAsync(connection, buffer, 0, count, pieceMessage == null?null : rateLimiter, peerMonitor?.ProtocolUp, managerMonitor?.ProtocolUp).ConfigureAwait(false); if (pieceMessage != null) { peerMonitor?.ProtocolUp.AddDelta(-pieceMessage.RequestLength); managerMonitor?.ProtocolUp.AddDelta(-pieceMessage.RequestLength); peerMonitor?.DataUp.AddDelta(pieceMessage.RequestLength); managerMonitor?.DataUp.AddDelta(pieceMessage.RequestLength); } } }
/// <summary> /// /// </summary> /// <param name="manager">The torrent which the peer is associated with.</param> /// <param name="id">The peer whose message queue you want to start processing</param> internal async void TryProcessQueue(TorrentManager manager, PeerId id) { if (!id.MessageQueue.BeginProcessing()) { return; } await MainLoop.SwitchToThreadpool(); ByteBufferPool.Releaser messageBuffer = default; ByteBufferPool.Releaser pieceBuffer = default; PeerMessage msg; try { while ((msg = id.MessageQueue.TryDequeue()) != null) { var msgLength = msg.ByteLength; if (msg is PieceMessage pm) { if (pieceBuffer.Buffer == null) { pieceBuffer = DiskManager.BufferPool.Rent(msgLength, out ByteBuffer _); } pm.DataReleaser = pieceBuffer; try { await DiskManager.ReadAsync(manager, pm.StartOffset + ((long)pm.PieceIndex * manager.Torrent.PieceLength), pm.Data, pm.RequestLength).ConfigureAwait(false); } catch (Exception ex) { await ClientEngine.MainLoop; manager.TrySetError(Reason.ReadFailure, ex); return; } System.Threading.Interlocked.Increment(ref id.piecesSent); } else { pieceBuffer.Dispose(); } if (messageBuffer.Buffer == null || messageBuffer.Buffer.Data.Length < msg.ByteLength) { messageBuffer.Dispose(); messageBuffer = NetworkIO.BufferPool.Rent(msgLength, out ByteBuffer _); } await PeerIO.SendMessageAsync(id.Connection, id.Encryptor, msg, manager.UploadLimiters, id.Monitor, manager.Monitor, messageBuffer.Buffer).ConfigureAwait(false); if (msg is PieceMessage) { System.Threading.Interlocked.Decrement(ref id.isRequestingPiecesCount); } id.LastMessageSent.Restart(); } } catch { await ClientEngine.MainLoop; CleanupSocket(manager, id); } finally { messageBuffer.Dispose(); pieceBuffer.Dispose(); } }
public static async ReusableTask ConnectAsync(IPeerConnection connection) { await MainLoop.SwitchToThreadpool(); await connection.ConnectAsync(); }