From 8f47feaf45c73e70bea11cff578ac0e4f86cdf5a Mon Sep 17 00:00:00 2001 From: "alan.mcgovern" Date: Tue, 14 Jun 2022 22:41:00 +0100 Subject: [PATCH] [core] Port hashes and metadatamode to IPiecePicker The engine now uses the exact same logic for 'picking' bittorrent v2 hashes, fetching the metadata and also regular piece/block downloading. This makes failure modes much easier to reason about. Additionally, it's much easier to ensure that the data is reliably fetched from other peers as the fault tolerance of the piece picking engine will ensure things are received, or re-requested from another peer. --- src/Benchmark/Program.cs | 27 ++- .../MonoTorrent.Client.Modes/MetadataMode.cs | 156 ++++++++------- .../PieceHashesMode.cs | 182 ++++++++++-------- .../MonoTorrent.Client/ClientEngine.cs | 2 +- .../Managers/PieceManager.cs | 34 +++- .../Managers/TorrentManager.cs | 26 ++- .../PeerId.PieceRequester.cs | 31 +-- .../Settings/EngineSettingsBuilder.cs | 2 + .../MonoTorrent/PieceHashesV2.cs | 7 + src/MonoTorrent.Client/MonoTorrent/Torrent.cs | 4 +- .../LTMetadata.cs | 15 +- .../MonoTorrent.PiecePicking/IPiecePicker.cs | 19 +- .../IPiecePickerExtensions.cs | 15 +- .../IgnoringPicker.cs | 2 +- .../PiecePickerFilter.cs | 13 +- .../PriorityPicker.cs | 2 +- .../RandomisedPicker.cs | 2 +- .../RarestFirstPicker.cs | 10 +- .../StandardPicker.cs | 47 +++-- .../StandardPieceRequester.cs | 115 +++++------ .../StreamingPieceRequester.cs | 90 +++++---- .../MonoTorrent.PiecePicking/IPeer.cs | 11 -- .../IPieceRequester.cs | 26 ++- .../PieceRequesterExtensions.cs | 60 ++++++ .../PieceRequesterSettings.cs | 27 ++- src/MonoTorrent/MonoTorrent/ITorrentInfo.cs | 3 + .../SampleClient/StandardDownloader.cs | 5 +- .../MetadataModeTests.2.cs | 118 ++++++++++++ .../MetadataModeTests.cs | 4 +- .../PieceHashesModeTests.cs | 143 ++++++++++++++ .../Tests.MonoTorrent.PiecePicking/PeerId.cs | 3 +- .../PiecePickerFilterChecker.cs | 8 +- .../PriorityPickerTests.cs | 6 +- .../RandomisedPickerTests.cs | 8 +- .../RarestFirstPickerTests.cs | 15 +- .../StandardPickerTests.cs | 179 ++++++++--------- .../StreamingPieceRequesterTests.cs | 14 +- src/Tests/TorrentInfoHelpers.cs | 32 ++- 38 files changed, 962 insertions(+), 501 deletions(-) create mode 100644 src/MonoTorrent/MonoTorrent.PiecePicking/PieceRequesterExtensions.cs rename src/{ => MonoTorrent}/MonoTorrent.PiecePicking/PieceRequesterSettings.cs (52%) create mode 100644 src/Tests/Tests.MonoTorrent.Client/MonoTorrent.Client.Modes/MetadataModeTests.2.cs create mode 100644 src/Tests/Tests.MonoTorrent.Client/MonoTorrent.Client.Modes/PieceHashesModeTests.cs diff --git a/src/Benchmark/Program.cs b/src/Benchmark/Program.cs index ca7eed9c8..79bba1fb9 100644 --- a/src/Benchmark/Program.cs +++ b/src/Benchmark/Program.cs @@ -205,7 +205,7 @@ class TorrentInfo : ITorrentInfo public string Name => "Name"; } - class TorrentData : ITorrentManagerInfo, IPieceRequesterData + class TorrentData : IPieceRequesterData { const int PieceCount = 500; @@ -218,10 +218,10 @@ class TorrentData : ITorrentManagerInfo, IPieceRequesterData int IPieceRequesterData.PieceCount => TorrentInfo.PieceCount (); int IPieceRequesterData.PieceLength => TorrentInfo.PieceLength; - public IPeer CreatePeer () + public Peer CreatePeer () => new Peer (PieceCount); - int IPieceRequesterData.BlocksPerPiece (int piece) + int IPieceRequesterData.SegmentsPerPiece (int piece) => TorrentInfo.BlocksPerPiece (piece); int IPieceRequesterData.ByteOffsetToPieceIndex (long byteOffset) @@ -230,6 +230,25 @@ int IPieceRequesterData.ByteOffsetToPieceIndex (long byteOffset) int IPieceRequesterData.BytesPerPiece (int piece) => TorrentInfo.BytesPerPiece (piece); + public void EnqueueRequest (IPeer peer, PieceSegment block) + { + + } + + public void EnqueueRequests (IPeer peer, Span blocks) + { + + } + + public void EnqueueCancellation (IPeer peer, PieceSegment segment) + { + + } + + public void EnqueueCancellations (IPeer peer, Span segments) + { + + } } class Peer : IPeer @@ -257,7 +276,7 @@ public Peer (int pieceCount) readonly TorrentData Data; readonly StandardPicker Picker; - readonly IPeer Requester; + readonly Peer Requester; readonly Queue Requested; public StandardPickerBenchmark () diff --git a/src/MonoTorrent.Client/MonoTorrent.Client.Modes/MetadataMode.cs b/src/MonoTorrent.Client/MonoTorrent.Client.Modes/MetadataMode.cs index cefcc24a9..111b4573d 100644 --- a/src/MonoTorrent.Client/MonoTorrent.Client.Modes/MetadataMode.cs +++ b/src/MonoTorrent.Client/MonoTorrent.Client.Modes/MetadataMode.cs @@ -28,6 +28,7 @@ using System; +using System.Collections.Generic; using System.IO; using System.Security.Cryptography; using System.Threading; @@ -39,6 +40,7 @@ using MonoTorrent.Messages.Peer; using MonoTorrent.Messages.Peer.FastPeer; using MonoTorrent.Messages.Peer.Libtorrent; +using MonoTorrent.PiecePicking; namespace MonoTorrent.Client.Modes { @@ -46,15 +48,63 @@ class MetadataMode : Mode { static readonly Logger logger = Logger.Create (nameof (MetadataMode)); - BitField? bitField; + class MetadataData : IPieceRequesterData, IMessageEnqueuer + { + public IList Files => Array.Empty (); + public int PieceCount => 1; + public int PieceLength { get; } + + int length; + + public MetadataData (int size) + { + length = size; + PieceLength = (int) Math.Pow (2, Math.Ceiling (Math.Log (size, 2)) + 1); + } + + public int BytesPerBlock(int pieceIndex, int blockIndex) + => Math.Min (Constants.BlockSize, BytesPerPiece (pieceIndex) - blockIndex * Constants.BlockSize); + + public int SegmentsPerPiece (int piece) + => (length + Constants.BlockSize - 1) / Constants.BlockSize; + + public int ByteOffsetToPieceIndex (long byteOffset) + => 0; + + public int BytesPerPiece (int piece) + => length; + + void IMessageEnqueuer.EnqueueRequest (IPeer peer, PieceSegment block) + { + var message = new LTMetadata (((PeerId) peer).ExtensionSupports, LTMetadata.MessageType.Request, block.BlockIndex); + ((PeerId) peer).MessageQueue.Enqueue (message); + } + + void IMessageEnqueuer.EnqueueRequests (IPeer peer, Span blocks) + { + foreach (var block in blocks) + ((IMessageEnqueuer)this).EnqueueRequest (peer, block); + } + + void IMessageEnqueuer.EnqueueCancellation (IPeer peer, PieceSegment segment) + { + // you can't cancel a request for metadata + } + + void IMessageEnqueuer.EnqueueCancellations (IPeer peer, Span segments) + { + // you can't cancel a request for metadata + } + } + static readonly TimeSpan timeout = TimeSpan.FromSeconds (10); - PeerId? currentId; string savePath; - DateTime requestTimeout; bool stopWhenDone; bool HasAnnounced { get; set; } - MemoryStream? Stream { get; set; } + MetadataData? RequesterData { get; set; } + IPieceRequester? Requester { get; set; } + byte[]? Stream { get; set; } public override bool CanHashCheck => true; public override TorrentState State => TorrentState.Metadata; @@ -72,6 +122,13 @@ public MetadataMode (TorrentManager manager, DiskManager diskManager, Connection this.stopWhenDone = stopWhenDone; } + public override void HandlePeerDisconnected (PeerId id) + { + base.HandlePeerDisconnected (id); + if (Requester != null && RequesterData != null) + Requester.CancelRequests (id, 0, RequesterData.PieceCount); + } + public override void Tick (int counter) { if (!HasAnnounced) { @@ -79,16 +136,9 @@ public override void Tick (int counter) SendAnnounces (); } - //if one request have been sent and we have wait more than timeout - // request the next peer - if (requestTimeout < DateTime.Now) { - NextPeer (); - - if (currentId != null && Stream != null) { - RequestNextNeededPiece (currentId); - } - } - + foreach (PeerId id in Manager.Peers.ConnectedPeers) + if (id.SupportsLTMessages && id.ExtensionSupports.Supports (LTMetadata.Support.Name)) + RequestNextNeededPiece (id); } async void SendAnnounces () @@ -104,60 +154,33 @@ await Task.WhenAll ( } } - void NextPeer () - { - bool flag = false; - - foreach (PeerId id in Manager.Peers.ConnectedPeers) { - if (id.SupportsLTMessages && id.ExtensionSupports.Supports (LTMetadata.Support.Name)) { - if (id == currentId) - flag = true; - else if (flag) { - currentId = id; - return; - } - } - } - //second pass without removing the currentid and previous ones - foreach (PeerId id in Manager.Peers.ConnectedPeers) { - if (id.SupportsLTMessages && id.ExtensionSupports.Supports (LTMetadata.Support.Name)) { - currentId = id; - return; - } - } - currentId = null; - return; - } - protected override void HandleLtMetadataMessage (PeerId id, LTMetadata message) { base.HandleLtMetadataMessage (id, message); + if (Requester is null || RequesterData is null || Stream is null) + return; + switch (message.MetadataMessageType) { case LTMetadata.MessageType.Data: - if (Stream is null || bitField is null) - throw new Exception ("Need extention handshake before ut_metadata message."); - - // If we've already received everything successfully, do nothing! - if (bitField.AllTrue) + if (!Requester.ValidatePiece (id, new PieceSegment (0, message.Piece), out bool pieceComplete, out IList peersInvolved)) return; - Stream.Seek (message.Piece * LTMetadata.BlockSize, SeekOrigin.Begin); - Stream.Write (message.MetadataPiece, 0, message.MetadataPiece.Length); - bitField[message.Piece] = true; - if (bitField.AllTrue) { + message.MetadataPiece.CopyTo (Stream.AsMemory (message.Piece * LTMetadata.BlockSize)); + if (pieceComplete) { InfoHash? v1InfoHash; InfoHash? v2InfoHash; - Stream.Position = 0; + using (SHA1 hasher = SHA1.Create ()) v1InfoHash = InfoHash.FromMemory (hasher.ComputeHash (Stream)); + using (SHA256 hasher = SHA256.Create ()) v2InfoHash = InfoHash.FromMemory (hasher.ComputeHash (Stream)); if (!Manager.InfoHashes.Contains (v1InfoHash) && !Manager.InfoHashes.Contains (v2InfoHash)) { - bitField.SetAll (false); + // Do nothing. As the piece has been marked as 'complete' by the picker, the internal picker state has dropped all references to the piece. + // We'll automatically retry downloading all pieces now. } else { - Stream.Position = 0; BEncodedDictionary dict = new BEncodedDictionary (); dict.Add ("info", BEncodedValue.Decode (Stream)); @@ -177,7 +200,11 @@ protected override void HandleLtMetadataMessage (PeerId id, LTMetadata message) } var rawData = dict.Encode (); if (Torrent.TryLoad (rawData, out Torrent? t)) { + Requester = null; + RequesterData = null; + if (stopWhenDone) { + Manager.SetMetadata (t); Manager.RaiseMetadataReceived (rawData); return; } @@ -198,7 +225,8 @@ protected override void HandleLtMetadataMessage (PeerId id, LTMetadata message) _ = Manager.StartAsync (); Manager.RaiseMetadataReceived (rawData); } else { - bitField.SetAll (false); + // Do nothing. As the piece has been marked as 'complete' by the picker, the internal picker state has dropped all references to the piece. + // We'll automatically retry downloading all pieces now. } } } @@ -243,19 +271,12 @@ protected override void HandleInterestedMessage (PeerId id, InterestedMessage me // Nothing } - int pieceToRequest; void RequestNextNeededPiece (PeerId id) { - if (bitField is null || bitField.AllTrue) + if (Requester is null ) return; - while (bitField[pieceToRequest % bitField.Length]) - pieceToRequest++; - - pieceToRequest = pieceToRequest % bitField.Length; - var m = new LTMetadata (id.ExtensionSupports, LTMetadata.MessageType.Request, pieceToRequest++); - id.MessageQueue.Enqueue (m); - requestTimeout = DateTime.Now.Add (timeout); + Requester.AddRequests (id, id.BitField, Array.Empty ()); } protected override void AppendBitfieldMessage (PeerId id, MessageBundle bundle) @@ -283,19 +304,16 @@ protected override void HandleExtendedHandshakeMessage (PeerId id, ExtendedHands if (id.ExtensionSupports.Supports (LTMetadata.Support.Name)) { var metadataSize = message.MetadataSize.GetValueOrDefault (0); if (Stream == null && metadataSize > 0) { - Stream = new MemoryStream (new byte[metadataSize], 0, metadataSize, true, true); - int size = metadataSize % LTMetadata.BlockSize; - if (size > 0) - size = 1; - size += metadataSize / LTMetadata.BlockSize; - bitField = new BitField (size); + Stream = new byte[metadataSize]; + Requester = Manager.Engine!.Factories.CreatePieceRequester (new PieceRequesterSettings (false, false, false, ignoreBitFieldAndChokeState: true)); + RequesterData = new MetadataData (metadataSize); + Requester.Initialise (RequesterData, RequesterData, Array.Empty ()); } // We only create the Stream if the remote peer has sent the metadata size key in their handshake. // There's no guarantee the remote peer has the metadata yet, so even though they support metadata // mode they might not be able to share the data. - if (Stream != null) - RequestNextNeededPiece (id); + RequestNextNeededPiece (id); } } diff --git a/src/MonoTorrent.Client/MonoTorrent.Client.Modes/PieceHashesMode.cs b/src/MonoTorrent.Client/MonoTorrent.Client.Modes/PieceHashesMode.cs index 035d0fb1c..44deecd57 100644 --- a/src/MonoTorrent.Client/MonoTorrent.Client.Modes/PieceHashesMode.cs +++ b/src/MonoTorrent.Client/MonoTorrent.Client.Modes/PieceHashesMode.cs @@ -46,15 +46,68 @@ namespace MonoTorrent.Client.Modes { class PieceHashesMode : Mode { - static readonly PeerId CompletedSentinal = PeerId.CreateNull (1); const int MaxHashesPerRequest = 512; + + class HashesRequesterData : IPieceRequesterData, IMessageEnqueuer + { + public BitField ValidatedPieces { get; } + public IList Files => Array.Empty (); + public int PieceCount => (totalHashes + PieceLength - 1) / PieceLength; + public int PieceLength => MaxHashesPerRequest; + + readonly int actualPieceLength; + readonly MerkleRoot root; + readonly int totalHashes; + + public HashesRequesterData (MerkleRoot root, int actualPieceLength, int totalHashes) + { + this.actualPieceLength = actualPieceLength; + this.root = root; + this.totalHashes = totalHashes; + ValidatedPieces = new BitField (PieceCount); + } + + public int SegmentsPerPiece (int piece) + => 1; + + public int ByteOffsetToPieceIndex (long byteOffset) + => (int) (byteOffset / PieceLength); + + public int BytesPerPiece (int pieceIndex) + => pieceIndex == PieceCount - 1 ? totalHashes - pieceIndex * PieceLength : PieceLength; + + void IMessageEnqueuer.EnqueueRequest (IPeer peer, PieceSegment block) + { + var message = HashRequestMessage.Create (root, totalHashes, actualPieceLength, block.PieceIndex * PieceLength, MaxHashesPerRequest); + ((PeerId) peer).MessageQueue.Enqueue (message); + } + + void IMessageEnqueuer.EnqueueRequests (IPeer peer, Span blocks) + { + foreach (var block in blocks) + ((IMessageEnqueuer) this).EnqueueRequest (peer, block); + } + + void IMessageEnqueuer.EnqueueCancellation (IPeer peer, PieceSegment segment) + { + // You can't cancel a request for hashes + } + + void IMessageEnqueuer.EnqueueCancellations (IPeer peer, Span segments) + { + // you can't cancel a request for hashes + } + } + + static readonly PeerId CompletedSentinal = PeerId.CreateNull (1); const int SHA256HashLength = 32; static readonly Logger logger = Logger.Create (nameof (PieceHashesMode)); ValueStopwatch LastAnnounced { get; set; } public override TorrentState State => TorrentState.FetchingHashes; - Dictionary pickers; + Dictionary pickers; Dictionary infoHashes; + HashSet IgnoredPeers { get; } public PieceHashesMode (TorrentManager manager, DiskManager diskManager, ConnectionManager connectionManager, EngineSettings settings) : base (manager, diskManager, connectionManager, settings) @@ -62,13 +115,14 @@ public PieceHashesMode (TorrentManager manager, DiskManager diskManager, Connect if (manager.Torrent is null) throw new InvalidOperationException ($"{nameof (PieceHashesMode)} can only be used after the torrent metadata is available"); - pickers = manager.Torrent!.Files.ToDictionary (t => t, value => new PeerId?[(value.EndPieceIndex - value.StartPieceIndex + MaxHashesPerRequest) / MaxHashesPerRequest]); + pickers = manager.Torrent!.Files.Where (t => t.StartPieceIndex != t.EndPieceIndex).ToDictionary (t => t, value => { + var data = new HashesRequesterData (value.PiecesRoot, manager.Torrent.PieceLength, value.EndPieceIndex - value.StartPieceIndex + 1); + var request = Manager.Engine!.Factories.CreatePieceRequester (new PieceRequesterSettings (false, false, false, ignoreBitFieldAndChokeState: true)); + request.Initialise (data, data, new ReadOnlyBitField[] { data.ValidatedPieces } ); + return (request, data); + }); infoHashes = manager.Torrent.Files.Where (t => t.EndPieceIndex != t.StartPieceIndex).ToDictionary (t => t, value => new MerkleLayers (value.PiecesRoot, manager.Torrent.PieceLength, value.EndPieceIndex - value.StartPieceIndex + 1)); - - // Files with 1 piece do not have additional hashes to fetch. The PiecesRoot *is* the SHA256 of the entire file. - foreach (var value in pickers) - if (value.Key.EndPieceIndex == value.Key.StartPieceIndex) - value.Value[0] = CompletedSentinal; + IgnoredPeers = new HashSet (); } public override void Tick (int counter) @@ -93,115 +147,81 @@ await Task.WhenAll ( } } - List Peers = new List (); + + void MaybeRequestNext () + { + foreach (var peer in Manager.Peers.ConnectedPeers) { + if (IgnoredPeers.Contains (peer)) + continue; + foreach (var picker in pickers) { + if (!picker.Value.Item2.ValidatedPieces.AllTrue) + picker.Value.Item1.AddRequests (peer, peer.BitField, Array.Empty ()); + } + } + } public override void HandlePeerConnected (PeerId id) { base.HandlePeerConnected (id); - Peers.Add (id); + MaybeRequestNext (); } public override void HandlePeerDisconnected (PeerId id) { base.HandlePeerDisconnected (id); - Peers.Remove (id); - } - - void MaybeRequestNext () - { - if (Peers.Count == 0) - return; - - foreach (var picker in pickers) { - for (int i = 0; i < picker.Value.Length; i++) { - // Successfully downloaded! - if (picker.Value[i] == CompletedSentinal) - continue; - - if (picker.Value[i] == null) { - picker.Value[i] = RequestChunk (picker.Key, i * MaxHashesPerRequest); - return; - } else if (!picker.Value[i]!.IsConnected) { - // We'll request this on the next tick. - picker.Value[i] = null; - } - } - } + foreach (var picker in pickers) + picker.Value.Item1.CancelRequests (id, 0, picker.Key.EndPieceIndex - picker.Key.StartPieceIndex + 1); } protected override void HandleHashRejectMessage (PeerId id, HashRejectMessage hashRejectMessage) { // If someone rejects us, let's remove them from the list for now... base.HandleHashRejectMessage (id, hashRejectMessage); - Peers.Remove (id); - RemoveRequest (id, hashRejectMessage.PiecesRoot, hashRejectMessage.Index / MaxHashesPerRequest); + var file = Manager.Torrent!.Files.FirstOrDefault (f => f.PiecesRoot.Span.SequenceEqual (hashRejectMessage.PiecesRoot.Span)); + if (file == null) + return; + + var picker = pickers[file]; + if (!picker.Item1.ValidatePiece (id, new PieceSegment (hashRejectMessage.Index / MaxHashesPerRequest, 0), out _, out _)) + return; + + IgnoredPeers.Add (id); } protected override void HandleHashesMessage (PeerId id, HashesMessage hashesMessage) { base.HandleHashesMessage (id, hashesMessage); - RemoveRequest (id, hashesMessage.PiecesRoot, hashesMessage.Index / MaxHashesPerRequest); var file = Manager.Torrent!.Files.FirstOrDefault (f => f.PiecesRoot.Span.SequenceEqual (hashesMessage.PiecesRoot.Span)); if (file == null) return; - var actual = Manager.Torrent.CreatePieceHashes ().TryGetV2Hashes (hashesMessage.PiecesRoot); + + var picker = pickers[file]; + if (!picker.Item1.ValidatePiece (id, new PieceSegment (hashesMessage.Index / MaxHashesPerRequest, 0), out _, out _)) { + ConnectionManager.CleanupSocket (Manager, id); + return; + } + // NOTE: Tweak this so we validate the hash in-place, and ensure the data we've been given, provided with the ancestor // hashes, combines to form the `PiecesRoot` value. - var memory = infoHashes[file].TryAppend (hashesMessage.BaseLayer, hashesMessage.Index, hashesMessage.Length, hashesMessage.Hashes.Span.Slice (0, hashesMessage.Length * 32), hashesMessage.Hashes.Span.Slice (hashesMessage.Length * 32)); - MarkDone (hashesMessage.PiecesRoot, hashesMessage.Index / MaxHashesPerRequest); + var success = infoHashes[file].TryAppend (hashesMessage.BaseLayer, hashesMessage.Index, hashesMessage.Length, hashesMessage.Hashes.Span.Slice (0, hashesMessage.Length * 32), hashesMessage.Hashes.Span.Slice (hashesMessage.Length * 32)); + if (success) + picker.Item2.ValidatedPieces[hashesMessage.Index / MaxHashesPerRequest] = true; - if (pickers[file].All (t => t == CompletedSentinal)) { + if (picker.Item2.ValidatedPieces.AllTrue) { using var hasher = IncrementalHash.CreateHash (HashAlgorithmName.SHA256); if (!infoHashes[file].TryVerify (out ReadOnlyMerkleLayers? verifiedPieceHashes)) - pickers[file].AsSpan ().Clear (); + picker.Item2.ValidatedPieces.SetAll (false); } - if (pickers.All (picker => picker.Value.All (t => t == CompletedSentinal))) { + if (pickers.All (picker => picker.Value.Item2.ValidatedPieces.AllTrue)) { Manager.PieceHashes = Manager.Torrent.CreatePieceHashes (infoHashes.ToDictionary (t => t.Key.PiecesRoot, v => (v.Value.TryVerify (out var root) ? root : null)!)); + Manager.PendingV2PieceHashes.SetAll (false); Manager.Mode = new DownloadMode (Manager, DiskManager, ConnectionManager, Settings); } - } - void MarkDone (MerkleRoot piecesRoot, int requestOffset) - { - var file = Manager.Torrent!.Files.FirstOrDefault (t => t.PiecesRoot == piecesRoot); - if (file == null) - return; - - var picker = pickers[file]; - picker[requestOffset] = CompletedSentinal; - } - - void RemoveRequest (PeerId id, MerkleRoot piecesRoot, int requestOffset) - { - var file = Manager.Torrent!.Files.FirstOrDefault (t => t.PiecesRoot ==piecesRoot); - if (file == null) - return; - - var picker = pickers[file]; - if (picker[requestOffset] != id) { - id.Connection.Dispose (); - throw new InvalidOperationException ("Something bad happened and the peer rejected a request we did not send"); - } - picker[requestOffset] = null; - } - - PeerId RequestChunk (ITorrentFile file, int hashOffset) - { - int totalHashes = file.EndPieceIndex - file.StartPieceIndex + 1; - var hashesRequested = Math.Min (MaxHashesPerRequest, totalHashes - hashOffset); - - var preferredPeer = Peers[0]; - Peers.RemoveAt (0); - Peers.Add (preferredPeer); - - var leafLayer = (int) Math.Log (Constants.BlockSize, 2); - var pieceLayer = (int) Math.Log (Manager.Torrent!.PieceLength, 2) - leafLayer; - var proofLayers = (int) Math.Ceiling (Math.Log (file.Length / (double)Manager.Torrent.PieceLength, 2)) - 1; - preferredPeer.MessageQueue.Enqueue (new HashRequestMessage (file.PiecesRoot, pieceLayer, hashOffset, hashesRequested, proofLayers)); - return preferredPeer; + MaybeRequestNext (); } } } diff --git a/src/MonoTorrent.Client/MonoTorrent.Client/ClientEngine.cs b/src/MonoTorrent.Client/MonoTorrent.Client/ClientEngine.cs index df551f971..0f726561a 100644 --- a/src/MonoTorrent.Client/MonoTorrent.Client/ClientEngine.cs +++ b/src/MonoTorrent.Client/MonoTorrent.Client/ClientEngine.cs @@ -360,7 +360,7 @@ public async Task AddAsync (Torrent torrent, string saveDirector await MainLoop.SwitchThread (); var editor = new TorrentEditor (new BEncodedDictionary { - { "info", BEncodedValue.Decode (torrent.InfoMetadata) } + { "info", BEncodedValue.Decode (torrent.InfoMetadata.Span) } }); editor.SetCustom ("name", (BEncodedString) torrent.Name); diff --git a/src/MonoTorrent.Client/MonoTorrent.Client/Managers/PieceManager.cs b/src/MonoTorrent.Client/MonoTorrent.Client/Managers/PieceManager.cs index b4d553118..f0cf8c9f5 100644 --- a/src/MonoTorrent.Client/MonoTorrent.Client/Managers/PieceManager.cs +++ b/src/MonoTorrent.Client/MonoTorrent.Client/Managers/PieceManager.cs @@ -66,7 +66,9 @@ internal PieceManager (TorrentManager manager) internal bool PieceDataReceived (PeerId id, PieceMessage message, out bool pieceComplete, out IList peersInvolved) { - if (Initialised && Requester.ValidatePiece (id, new BlockInfo (message.PieceIndex, message.StartOffset, message.RequestLength), out pieceComplete, out peersInvolved)) { + //FIXME: Ensure piece length is correct? + var isValidLength = Manager.Torrent!.BytesPerBlock (message.PieceIndex, message.StartOffset / Constants.BlockSize) == message.RequestLength; + if (Initialised && isValidLength && Requester.ValidatePiece (id, new PieceSegment (message.PieceIndex, message.StartOffset / Constants.BlockSize), out pieceComplete, out peersInvolved)) { id.LastBlockReceived.Restart (); if (pieceComplete) PendingHashCheckPieces[message.PieceIndex] = true; @@ -91,16 +93,32 @@ internal bool IsInteresting (PeerId id) return Requester.IsInteresting (id, id.BitField); } + ReadOnlyBitField[] otherAvailableCache = Array.Empty (); internal void AddPieceRequests (PeerId id) { - if (Initialised) - Requester.AddRequests (id, Manager.Peers.ConnectedPeers); + if (Initialised) { + if (otherAvailableCache.Length < Manager.Peers.ConnectedPeers.Count) + otherAvailableCache = new ReadOnlyBitField[Manager.Peers.ConnectedPeers.Count]; + var otherAvailable = otherAvailableCache.AsSpan (0, Manager.Peers.ConnectedPeers.Count); + for (int i = 0; i < otherAvailable.Length; i++) + otherAvailable[i] = Manager.Peers.ConnectedPeers[i].BitField; + + Requester.AddRequests (id, id.BitField, otherAvailable); + } } + (IPeer, ReadOnlyBitField)[] peersCache = Array.Empty<(IPeer, ReadOnlyBitField )> (); + internal void AddPieceRequests (List peers) { - if (Initialised) - Requester.AddRequests (peers); + if (Initialised) { + if (peersCache.Length < peers.Count) + peersCache = new (IPeer, ReadOnlyBitField)[peers.Count]; + var span = peersCache.AsSpan (0, peers.Count); + for (int i = 0; i < peers.Count; i++) + span[i] = (peers[i], peers[i].BitField); + Requester.AddRequests (span); + } } internal void ChangePicker (IPieceRequester requester) @@ -123,7 +141,7 @@ internal void Initialise () PendingHashCheckPieces, Manager.UnhashedPieces, }; - Requester.Initialise (Manager, ignorableBitfieds); + Requester.Initialise (Manager, Manager, ignorableBitfieds); } } @@ -150,8 +168,8 @@ internal void CancelRequests (PeerId id) internal void RequestRejected (PeerId id, BlockInfo pieceRequest) { - if (Initialised) - Requester.RequestRejected (id, pieceRequest); + if (Initialised && Manager.Torrent!.BytesPerBlock (pieceRequest.PieceIndex, pieceRequest.StartOffset / Constants.BlockSize) == pieceRequest.RequestLength) + Requester.RequestRejected (id, pieceRequest.ToPieceSegment ()); } } } diff --git a/src/MonoTorrent.Client/MonoTorrent.Client/Managers/TorrentManager.cs b/src/MonoTorrent.Client/MonoTorrent.Client/Managers/TorrentManager.cs index 5881bc122..933595ba0 100644 --- a/src/MonoTorrent.Client/MonoTorrent.Client/Managers/TorrentManager.cs +++ b/src/MonoTorrent.Client/MonoTorrent.Client/Managers/TorrentManager.cs @@ -46,7 +46,7 @@ namespace MonoTorrent.Client { - public class TorrentManager : IEquatable, ITorrentManagerInfo, IPieceRequesterData + public class TorrentManager : IEquatable, ITorrentManagerInfo, IPieceRequesterData, IMessageEnqueuer { #region Events @@ -1126,11 +1126,33 @@ internal void HandlePeerConnected (PeerId id) IList IPieceRequesterData.Files => Files; int IPieceRequesterData.PieceCount => Torrent == null ? 0 : Torrent.PieceCount; int IPieceRequesterData.PieceLength => Torrent == null ? 0 : Torrent.PieceLength; - int IPieceRequesterData.BlocksPerPiece (int pieceIndex) + int IPieceRequesterData.SegmentsPerPiece (int pieceIndex) => Torrent == null ? 0 : Torrent.BlocksPerPiece (pieceIndex); int IPieceRequesterData.ByteOffsetToPieceIndex (long byteOffset) => Torrent == null ? 0 : Torrent.ByteOffsetToPieceIndex (byteOffset); int IPieceRequesterData.BytesPerPiece (int piece) => Torrent == null ? 0 : Torrent.BytesPerPiece (piece); + void IMessageEnqueuer.EnqueueRequest (IPeer peer, PieceSegment block) + => ((IMessageEnqueuer) this).EnqueueRequests (peer, stackalloc PieceSegment[] { block }); + void IMessageEnqueuer.EnqueueRequests (IPeer peer, Span segments) + { + (var bundle, var releaser) = RequestBundle.Rent (); + bundle.Initialize (segments.ToBlockInfo (stackalloc BlockInfo[segments.Length], this)); + ((PeerId) peer).MessageQueue.Enqueue (bundle, releaser); + } + + void IMessageEnqueuer.EnqueueCancellation (IPeer peer, PieceSegment segment) + { + (var msg, var releaser) = PeerMessage.Rent (); + var blockInfo = segment.ToBlockInfo (this); + msg.Initialize (blockInfo.PieceIndex, blockInfo.StartOffset, blockInfo.RequestLength); + ((PeerId) peer).MessageQueue.Enqueue (msg, releaser); + } + + void IMessageEnqueuer.EnqueueCancellations (IPeer peer, Span segments) + { + for (int i = 0; i < segments.Length; i++) + ((IMessageEnqueuer) this).EnqueueCancellation (peer, segments[i]); + } } } diff --git a/src/MonoTorrent.Client/MonoTorrent.Client/PeerId.PieceRequester.cs b/src/MonoTorrent.Client/MonoTorrent.Client/PeerId.PieceRequester.cs index 9a548b163..3560b29df 100644 --- a/src/MonoTorrent.Client/MonoTorrent.Client/PeerId.PieceRequester.cs +++ b/src/MonoTorrent.Client/MonoTorrent.Client/PeerId.PieceRequester.cs @@ -36,7 +36,7 @@ namespace MonoTorrent.Client { - public partial class PeerId : IPeerWithMessaging + public partial class PeerId : IPeer { int IPeer.AmRequestingPiecesCount { get => AmRequestingPiecesCount; set => AmRequestingPiecesCount = value; } bool IPeer.CanRequestMorePieces { @@ -57,37 +57,8 @@ bool IPeer.CanRequestMorePieces { int IPeer.RepeatedHashFails => Peer.RepeatedHashFails; List IPeer.SuggestedPieces => SuggestedPieces; bool IPeer.CanCancelRequests => SupportsFastPeer; - int IPeer.TotalHashFails => Peer.TotalHashFails; int IPeer.MaxPendingRequests => MaxPendingRequests; - void IPeerWithMessaging.EnqueueRequest (BlockInfo request) - { - Span buffer = stackalloc BlockInfo[1]; - buffer[0] = request; - ((IPeerWithMessaging) this).EnqueueRequests (buffer); - } - - void IPeerWithMessaging.EnqueueRequests (Span requests) - { - - (var bundle, var releaser) = PeerMessage.Rent (); - bundle.Initialize (requests); - MessageQueue.Enqueue (bundle, releaser); - } - - void IPeerWithMessaging.EnqueueCancellation (BlockInfo request) - { - (var msg, var releaser) = PeerMessage.Rent (); - msg.Initialize (request.PieceIndex, request.StartOffset, request.RequestLength); - MessageQueue.Enqueue (msg, releaser); - } - - void IPeerWithMessaging.EnqueueCancellations (IList requests) - { - for (int i = 0; i < requests.Count; i++) - MessageQueue.Enqueue (new CancelMessage (requests[i].PieceIndex, requests[i].StartOffset, requests[i].RequestLength), default); - } - int IPeer.PreferredRequestAmount (int pieceLength) { if (Connection is HttpPeerConnection) { diff --git a/src/MonoTorrent.Client/MonoTorrent.Client/Settings/EngineSettingsBuilder.cs b/src/MonoTorrent.Client/MonoTorrent.Client/Settings/EngineSettingsBuilder.cs index 8bb81c852..378a06a95 100644 --- a/src/MonoTorrent.Client/MonoTorrent.Client/Settings/EngineSettingsBuilder.cs +++ b/src/MonoTorrent.Client/MonoTorrent.Client/Settings/EngineSettingsBuilder.cs @@ -47,6 +47,7 @@ internal static EngineSettings CreateForTests ( bool allowLocalPeerDiscovery = false, bool allowPortForwarding = false, bool automaticFastResume = false, + bool autoSaveLoadMagnetLinkMetadata = true, IPEndPoint? dhtEndPoint = null, IPEndPoint? listenEndPoint = null, string? cacheDirectory = null, @@ -56,6 +57,7 @@ internal static EngineSettings CreateForTests ( AllowLocalPeerDiscovery = allowLocalPeerDiscovery, AllowPortForwarding = allowPortForwarding, AutoSaveLoadFastResume = automaticFastResume, + AutoSaveLoadMagnetLinkMetadata = autoSaveLoadMagnetLinkMetadata, CacheDirectory = cacheDirectory ?? Path.Combine (Path.GetDirectoryName (typeof (EngineSettingsBuilder).Assembly.Location)!, "test_cache_dir"), DhtEndPoint = dhtEndPoint, ListenEndPoint = listenEndPoint, diff --git a/src/MonoTorrent.Client/MonoTorrent/PieceHashesV2.cs b/src/MonoTorrent.Client/MonoTorrent/PieceHashesV2.cs index 33272f88a..a69f2eeb6 100644 --- a/src/MonoTorrent.Client/MonoTorrent/PieceHashesV2.cs +++ b/src/MonoTorrent.Client/MonoTorrent/PieceHashesV2.cs @@ -51,6 +51,13 @@ class PieceHashesV2 : IPieceHashes public bool HasV2Hashes => true; + internal PieceHashesV2 (int pieceLength, IList files, BEncodedDictionary layers) + : this (pieceLength, + files, + layers.ToDictionary (kvp => new MerkleRoot (kvp.Key.AsMemory ()), kvp => ReadOnlyMerkleLayers.FromLayer (pieceLength, new MerkleRoot (kvp.Key.AsMemory ()), ((BEncodedString) kvp.Value).AsMemory ().Span)!)) + { + } + internal PieceHashesV2 (int pieceLength, IList files, Dictionary layers) => (Files, Layers, HashCodeLength, Count, PieceLayer) = (files, layers, 32, files.Last ().EndPieceIndex + 1, (int) Math.Log (pieceLength / 16384, 2)); diff --git a/src/MonoTorrent.Client/MonoTorrent/Torrent.cs b/src/MonoTorrent.Client/MonoTorrent/Torrent.cs index e304938f6..855df2aac 100644 --- a/src/MonoTorrent.Client/MonoTorrent/Torrent.cs +++ b/src/MonoTorrent.Client/MonoTorrent/Torrent.cs @@ -256,7 +256,7 @@ public static bool TryLoad (Stream stream, [NotNullWhen (true)] out Torrent? tor /// /// The 'info' dictionary encoded as a byte array. /// - internal byte[] InfoMetadata { get; private set; } + internal ReadOnlyMemory InfoMetadata { get; private set; } /// /// Shows whether DHT is allowed or not. If it is a private torrent, no peer @@ -330,7 +330,7 @@ public static bool TryLoad (Stream stream, [NotNullWhen (true)] out Torrent? tor LoadInternal (torrentInformation, infoHashes); - if (InfoHashes is null || InfoMetadata is null) + if (InfoHashes is null || InfoMetadata.IsEmpty) throw new InvalidOperationException ("One of the required properties was unset after deserializing torrent metadata"); } diff --git a/src/MonoTorrent.Messages/MonoTorrent.Messages.Peer.Libtorrent/LTMetadata.cs b/src/MonoTorrent.Messages/MonoTorrent.Messages.Peer.Libtorrent/LTMetadata.cs index f6912d7ad..420afc96d 100644 --- a/src/MonoTorrent.Messages/MonoTorrent.Messages.Peer.Libtorrent/LTMetadata.cs +++ b/src/MonoTorrent.Messages/MonoTorrent.Messages.Peer.Libtorrent/LTMetadata.cs @@ -57,7 +57,7 @@ public enum MessageType public int Piece { get; set; } - public byte[] MetadataPiece { get; set; } + public ReadOnlyMemory MetadataPiece { get; set; } public MessageType MetadataMessageType { get; internal set; } @@ -75,13 +75,13 @@ public LTMetadata (ExtensionSupports supportedExtensions, MessageType type, int } - public LTMetadata (ExtensionSupports supportedExtensions, MessageType type, int piece, byte[] metadata) + public LTMetadata (ExtensionSupports supportedExtensions, MessageType type, int piece, ReadOnlyMemory metadata) : this (supportedExtensions.MessageId (Support), type, piece, metadata) { } - public LTMetadata (byte extensionId, MessageType type, int piece, byte[] metadata) + public LTMetadata (byte extensionId, MessageType type, int piece, ReadOnlyMemory metadata) : this () { ExtensionId = extensionId; @@ -95,7 +95,7 @@ public LTMetadata (byte extensionId, MessageType type, int piece, byte[] metadat }; if (MetadataMessageType == MessageType.Data) { - if (metadata is null) + if (metadata.IsEmpty) throw new InvalidDataException ("The metadata data message did not contain any data."); dict.Add (TotalSizeKey, (BEncodedNumber) metadata.Length); } @@ -121,8 +121,9 @@ public override void Decode (ReadOnlySpan buffer) Piece = (int) ((BEncodedNumber) val).Number; if (d.TryGetValue (TotalSizeKey, out val)) { int totalSize = (int) ((BEncodedNumber) val).Number; - MetadataPiece = new byte[Math.Min (totalSize - Piece * BlockSize, BlockSize)]; - buffer.CopyTo (MetadataPiece); + var metadataPiece = new byte[Math.Min (totalSize - Piece * BlockSize, BlockSize)]; + buffer.CopyTo (metadataPiece); + MetadataPiece = metadataPiece; } } @@ -137,7 +138,7 @@ public override int Encode (Span buffer) if (MetadataMessageType == MessageType.Data) { var total = Math.Min (MetadataPiece.Length - Piece * BlockSize, BlockSize); - MetadataPiece.AsSpan (Piece * BlockSize, total).CopyTo (buffer); + MetadataPiece.Span.Slice (Piece * BlockSize, total).CopyTo (buffer); buffer = buffer.Slice (total); } return written - buffer.Length; diff --git a/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/IPiecePicker.cs b/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/IPiecePicker.cs index 923172d39..98dad0049 100644 --- a/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/IPiecePicker.cs +++ b/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/IPiecePicker.cs @@ -34,31 +34,26 @@ namespace MonoTorrent.PiecePicking { public interface IPiecePicker { - /// - /// Cancel all unreceived requests. No further blocks will be requested from this peer. - /// - /// The peer whose requests will be cancelled. - /// The number of requests which were cancelled - int AbortRequests (IPeer peer); - /// /// Cancel all unreceived requests between startIndex and endIndex. /// /// The peer to request the block from /// The lowest piece index to consider /// The highest piece index to consider - /// The list of requests which were cancelled - IList CancelRequests (IPeer peer, int startIndex, int endIndex); + /// + /// The number of entries written to the span + int CancelRequests (IPeer peer, int startIndex, int endIndex, Span cancellations); /// /// Request any unrequested block from a piece owned by this peer, or any other peer, within the specified bounds. /// /// The peer to request the block from + /// /// The lowest piece index to consider /// The highest piece index to consider /// The maximum number of concurrent duplicate requests /// - PieceSegment? ContinueAnyExistingRequest (IPeer peer, int startIndex, int endIndex, int maxDuplicateRequests); + PieceSegment? ContinueAnyExistingRequest (IPeer peer, ReadOnlyBitField available, int startIndex, int endIndex, int maxDuplicateRequests); /// /// Request the next unrequested block from a piece owned by this peer, within the specified bounds. @@ -114,12 +109,12 @@ public interface IPiecePicker /// /// /// - /// + /// /// /// /// /// - int PickPiece (IPeer peer, ReadOnlyBitField available, IReadOnlyList otherPeers, int startIndex, int endIndex, Span requests); + int PickPiece (IPeer peer, ReadOnlyBitField available, ReadOnlySpan otherAvailable, int startIndex, int endIndex, Span requests); /// /// Called when a piece is received from the . Returns true if the diff --git a/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/IPiecePickerExtensions.cs b/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/IPiecePickerExtensions.cs index 727113de9..8a30106d4 100644 --- a/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/IPiecePickerExtensions.cs +++ b/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/IPiecePickerExtensions.cs @@ -35,29 +35,24 @@ namespace MonoTorrent.PiecePicking { public static class IPiecePickerExtensions { - public static IList CancelRequests (this IPiecePicker picker, IPeer peer) - { - return picker.CancelRequests (peer, 0, peer.BitField.Length - 1); - } - - public static PieceSegment? ContinueAnyExistingRequest (this IPiecePicker picker, IPeer peer, int startIndex, int endIndex) - => picker.ContinueAnyExistingRequest (peer, startIndex, endIndex, 1); + public static PieceSegment? ContinueAnyExistingRequest (this IPiecePicker picker, IPeer peer, ReadOnlyBitField available, int startIndex, int endIndex) + => picker.ContinueAnyExistingRequest (peer, available, startIndex, endIndex, 1); public static PieceSegment? PickPiece (this IPiecePicker picker, IPeer peer, ReadOnlyBitField available) { Span buffer = stackalloc PieceSegment[1]; - var picked = picker.PickPiece (peer, available, Array.Empty (), 0, available.Length - 1, buffer); + var picked = picker.PickPiece (peer, available, Array.Empty (), 0, available.Length - 1, buffer); return picked == 1 ? (PieceSegment?) buffer[0] : null; } - public static PieceSegment? PickPiece (this IPiecePicker picker, IPeer peer, ReadOnlyBitField available, IReadOnlyList otherPeers) + public static PieceSegment? PickPiece (this IPiecePicker picker, IPeer peer, ReadOnlyBitField available, ReadOnlySpan otherPeers) { Span buffer = stackalloc PieceSegment[1]; var result = picker.PickPiece (peer, available, otherPeers, 0, available.Length - 1, buffer); return result == 1 ? (PieceSegment?) buffer[0] : null; } - public static int PickPiece (this IPiecePicker picker, IPeer peer, ReadOnlyBitField available, IReadOnlyList otherPeers, Span requests) + public static int PickPiece (this IPiecePicker picker, IPeer peer, ReadOnlyBitField available, ReadOnlySpan otherPeers, Span requests) { return picker.PickPiece (peer, available, otherPeers, 0, available.Length - 1, requests); } diff --git a/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/IgnoringPicker.cs b/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/IgnoringPicker.cs index 661384f06..b34605a9d 100644 --- a/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/IgnoringPicker.cs +++ b/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/IgnoringPicker.cs @@ -56,7 +56,7 @@ public override bool IsInteresting (IPeer peer, ReadOnlyBitField bitfield) => !Temp.From (bitfield).NAnd (Bitfield).AllFalse && base.IsInteresting (peer, Temp); - public override int PickPiece (IPeer peer, ReadOnlyBitField available, IReadOnlyList otherPeers, int startIndex, int endIndex, Span requests) + public override int PickPiece (IPeer peer, ReadOnlyBitField available, ReadOnlySpan otherPeers, int startIndex, int endIndex, Span requests) { // Invert 'bitfield' and AND it with the peers bitfield // Any pieces which are 'true' in the bitfield will not be downloaded diff --git a/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/PiecePickerFilter.cs b/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/PiecePickerFilter.cs index 067fd0531..4fd738a2e 100644 --- a/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/PiecePickerFilter.cs +++ b/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/PiecePickerFilter.cs @@ -39,14 +39,11 @@ public abstract class PiecePickerFilter : IPiecePicker protected PiecePickerFilter (IPiecePicker picker) => Next = picker; - public int AbortRequests (IPeer peer) - => Next.AbortRequests (peer); + public int CancelRequests (IPeer peer, int startIndex, int endIndex, Span cancellations) + => Next.CancelRequests (peer, startIndex, endIndex, cancellations); - public IList CancelRequests (IPeer peer, int startIndex, int endIndex) - => Next.CancelRequests (peer, startIndex, endIndex); - - public PieceSegment? ContinueAnyExistingRequest (IPeer peer, int startIndex, int endIndex, int maxDuplicateRequests) - => Next.ContinueAnyExistingRequest (peer, startIndex, endIndex, maxDuplicateRequests); + public PieceSegment? ContinueAnyExistingRequest (IPeer peer, ReadOnlyBitField available, int startIndex, int endIndex, int maxDuplicateRequests) + => Next.ContinueAnyExistingRequest (peer, available, startIndex, endIndex, maxDuplicateRequests); public PieceSegment? ContinueExistingRequest (IPeer peer, int startIndex, int endIndex) => Next.ContinueExistingRequest (peer, startIndex, endIndex); @@ -66,7 +63,7 @@ public virtual void Initialise (IPieceRequesterData torrentData) public virtual bool IsInteresting (IPeer peer, ReadOnlyBitField bitfield) => Next.IsInteresting (peer, bitfield); - public virtual int PickPiece (IPeer peer, ReadOnlyBitField available, IReadOnlyList otherPeers, int startIndex, int endIndex, Span requests) + public virtual int PickPiece (IPeer peer, ReadOnlyBitField available, ReadOnlySpan otherPeers, int startIndex, int endIndex, Span requests) => Next.PickPiece (peer, available, otherPeers, startIndex, endIndex, requests); public void RequestRejected (IPeer peer, PieceSegment request) diff --git a/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/PriorityPicker.cs b/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/PriorityPicker.cs index 508865863..748007682 100644 --- a/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/PriorityPicker.cs +++ b/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/PriorityPicker.cs @@ -104,7 +104,7 @@ public override bool IsInteresting (IPeer peer, ReadOnlyBitField bitfield) } } - public override int PickPiece (IPeer peer, ReadOnlyBitField available, IReadOnlyList otherPeers, int startIndex, int endIndex, Span requests) + public override int PickPiece (IPeer peer, ReadOnlyBitField available, ReadOnlySpan otherPeers, int startIndex, int endIndex, Span requests) { // Fast Path - the peer has nothing to offer if (available.AllFalse || temp == null) diff --git a/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/RandomisedPicker.cs b/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/RandomisedPicker.cs index ba490eabe..db204910c 100644 --- a/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/RandomisedPicker.cs +++ b/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/RandomisedPicker.cs @@ -42,7 +42,7 @@ public RandomisedPicker (IPiecePicker picker) Random = new Random (); } - public override int PickPiece (IPeer peer, ReadOnlyBitField available, IReadOnlyList otherPeers, int startIndex, int endIndex, Span requests) + public override int PickPiece (IPeer peer, ReadOnlyBitField available, ReadOnlySpan otherPeers, int startIndex, int endIndex, Span requests) { if (available.AllFalse) return 0; diff --git a/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/RarestFirstPicker.cs b/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/RarestFirstPicker.cs index 87953f030..e708dc75e 100644 --- a/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/RarestFirstPicker.cs +++ b/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/RarestFirstPicker.cs @@ -51,7 +51,7 @@ public override void Initialise (IPieceRequesterData torrentData) spares.Clear (); } - public override int PickPiece (IPeer peer, ReadOnlyBitField available, IReadOnlyList otherPeers, int startIndex, int endIndex, Span requests) + public override int PickPiece (IPeer peer, ReadOnlyBitField available, ReadOnlySpan otherPeers, int startIndex, int endIndex, Span requests) { if (available.AllFalse) return 0; @@ -73,7 +73,7 @@ public override int PickPiece (IPeer peer, ReadOnlyBitField available, IReadOnly return 0; } - void GenerateRarestFirst (ReadOnlyBitField peerBitfield, IReadOnlyList otherPeers) + void GenerateRarestFirst (ReadOnlyBitField peerBitfield, ReadOnlySpan otherPeers) { // Move anything in the rarest buffer into the spares while (rarest.Count > 0) @@ -85,8 +85,8 @@ void GenerateRarestFirst (ReadOnlyBitField peerBitfield, IReadOnlyList ot rarest.Push (current); // Get a cloned copy of the bitfield and begin iterating to find the rarest pieces - for (int i = 0; i < otherPeers.Count; i++) { - if (otherPeers[i].BitField.AllTrue) + for (int i = 0; i < otherPeers.Length; i++) { + if (otherPeers[i].AllTrue) continue; current = (spares.Count > 0 ? spares.Pop () : new BitField (current.Length)).From (current); @@ -94,7 +94,7 @@ void GenerateRarestFirst (ReadOnlyBitField peerBitfield, IReadOnlyList ot // currentBitfield = currentBitfield & (!otherBitfield) // This calculation finds the pieces this peer has that other peers *do not* have. // i.e. the rarest piece. - current.NAnd (otherPeers[i].BitField); + current.NAnd (otherPeers[i]); // If the bitfield now has no pieces we've completed our task if (current.AllFalse) { diff --git a/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/StandardPicker.cs b/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/StandardPicker.cs index 4fb4b3de8..00291161b 100644 --- a/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/StandardPicker.cs +++ b/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/StandardPicker.cs @@ -111,24 +111,26 @@ public partial class StandardPicker : IPiecePicker // static readonly Logger logger = Logger.Create (nameof(StandardPicker)); BitField? CanRequestBitField; + bool IgnoreChokeState { get; } PickedPieces? Requests { get; set; } IPieceRequesterData? TorrentData { get; set; } public StandardPicker () + : this (false) { } - public int AbortRequests (IPeer peer) + public StandardPicker (bool ignoreChokeState) { - return CancelWhere (b => peer == b.RequestedOff); + IgnoreChokeState = ignoreChokeState; } - public IList CancelRequests (IPeer peer, int startIndex, int endIndex) + public int CancelRequests (IPeer peer, int startIndex, int endIndex, Span cancellations) { if (Requests == null) - return Array.Empty (); + return 0; - IList? cancelled = null; + var length = cancellations.Length; foreach (var piece in Requests.Values) { if (piece.Index < startIndex || piece.Index > endIndex) continue; @@ -138,13 +140,13 @@ public IList CancelRequests (IPeer peer, int startIndex, int endIn if (!blocks[i].Received && blocks[i].RequestedOff == peer) { blocks[i].CancelRequest (); piece.Abandoned = true; - cancelled ??= new List (); - cancelled.Add (new PieceSegment (piece.Index, i)); + cancellations[0] = new PieceSegment (piece.Index, i); + cancellations.Slice (1); } } } - return cancelled ?? Array.Empty (); + return length - cancellations.Length; } public void RequestRejected (IPeer peer, PieceSegment rejectedRequest) @@ -228,13 +230,13 @@ public bool IsInteresting (IPeer peer, ReadOnlyBitField bitfield) return !bitfield.AllFalse; } - public int PickPiece (IPeer peer, ReadOnlyBitField available, IReadOnlyList otherPeers, int startIndex, int endIndex, Span requests) + public int PickPiece (IPeer peer, ReadOnlyBitField available, ReadOnlySpan otherPeers, int startIndex, int endIndex, Span requests) { PieceSegment? message; // If there is already a request on this peer, try to request the next block. If the peer is choking us, then the only // requests that could be continued would be existing "Fast" pieces. - if ((message = ContinueExistingRequest (peer, startIndex, endIndex, 1, false, false)) != null) { + if ((message = ContinueExistingRequest (peer, available, startIndex, endIndex, 1, false, false)) != null) { requests[0] = message.Value; return 1; } @@ -246,12 +248,12 @@ public int PickPiece (IPeer peer, ReadOnlyBitField available, IReadOnlyList ContinueExistingRequest (peer, startIndex, endIndex, 1, false, false); + => ContinueExistingRequest (peer, null, startIndex, endIndex, 1, false, false); - PieceSegment? ContinueExistingRequest (IPeer peer, int startIndex, int endIndex, int maxDuplicateRequests, bool allowAbandoned, bool allowAny) + PieceSegment? ContinueExistingRequest (IPeer peer, ReadOnlyBitField? availablePieces, int startIndex, int endIndex, int maxDuplicateRequests, bool allowAbandoned, bool allowAny) { if (Requests is null || TorrentData is null) return null; @@ -368,9 +370,12 @@ bool ValidateRequestWithPiece (IPeer peer, PieceSegment request, Piece piece, ou return null; } + if (availablePieces == null) + return null; + foreach (var p in Requests.Values) { int index = p.Index; - if (p.AllBlocksRequested || index < startIndex || index > endIndex || !peer.BitField[index]) + if (p.AllBlocksRequested || index < startIndex || index > endIndex || !availablePieces[index]) continue; // For each piece that was assigned to this peer, try to request a block from it @@ -387,7 +392,7 @@ bool ValidateRequestWithPiece (IPeer peer, PieceSegment request, Piece piece, ou // let's try and issue a duplicate! for (int duplicate = 1; duplicate < maxDuplicateRequests; duplicate++) { foreach (var primaryPiece in Requests.Values) { - if (primaryPiece.Index < startIndex || primaryPiece.Index > endIndex || !peer.BitField[primaryPiece.Index]) + if (primaryPiece.Index < startIndex || primaryPiece.Index > endIndex || !availablePieces[primaryPiece.Index]) continue; if (!Requests.TryGetDuplicates (primaryPiece.Index, out List? extraPieces)) { @@ -396,7 +401,7 @@ bool ValidateRequestWithPiece (IPeer peer, PieceSegment request, Piece piece, ou } if (extraPieces.Count < duplicate) { - var newPiece = PieceCache.Dequeue ().Initialise (primaryPiece.Index, TorrentData.BlocksPerPiece (primaryPiece.Index)); + var newPiece = PieceCache.Dequeue ().Initialise (primaryPiece.Index, TorrentData.SegmentsPerPiece (primaryPiece.Index)); for (int i = 0; i < primaryPiece.BlockCount; i++) if (primaryPiece.Blocks[i].Received) newPiece.Blocks[i].TrySetReceived (primaryPiece.Blocks[i].RequestedOff!); @@ -426,14 +431,14 @@ static bool HasAlreadyRequestedBlock (Piece piece, IList extraPieces, IPe return false; } - public PieceSegment? ContinueAnyExistingRequest (IPeer peer, int startIndex, int endIndex, int maxDuplicateRequests) + public PieceSegment? ContinueAnyExistingRequest (IPeer peer, ReadOnlyBitField available, int startIndex, int endIndex, int maxDuplicateRequests) { // If this peer is currently a 'dodgy' peer, then don't allow him to help with someone else's // piece request. if (peer.RepeatedHashFails != 0) return null; - return ContinueExistingRequest (peer, startIndex, endIndex, maxDuplicateRequests, true, true); + return ContinueExistingRequest (peer, available, startIndex, endIndex, maxDuplicateRequests, true, true); } PieceSegment? GetFromList (IPeer peer, ReadOnlyBitField bitfield, IList pieces) @@ -447,7 +452,7 @@ static bool HasAlreadyRequestedBlock (Piece piece, IList extraPieces, IPe continue; pieces.RemoveAt (i); - var p = PieceCache.Dequeue ().Initialise (index, TorrentData.BlocksPerPiece (index)); + var p = PieceCache.Dequeue ().Initialise (index, TorrentData.SegmentsPerPiece (index)); Requests.AddRequest (peer, p); return p.Blocks[0].CreateRequest (peer); } @@ -473,7 +478,7 @@ int GetStandardRequest (IPeer peer, ReadOnlyBitField current, int startIndex, in var totalRequested = 0; for (int i = 0; totalRequested < requests.Length && i < piecesNeeded; i++) { // Request the piece - var p = PieceCache.Dequeue ().Initialise (checkIndex + i, TorrentData.BlocksPerPiece (checkIndex + i)); + var p = PieceCache.Dequeue ().Initialise (checkIndex + i, TorrentData.SegmentsPerPiece (checkIndex + i)); Requests.AddRequest (peer, p); for (int j = 0; j < p.Blocks.Length && totalRequested < requests.Length; j++) requests[totalRequested++] = p.Blocks[j].CreateRequest (peer); diff --git a/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/StandardPieceRequester.cs b/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/StandardPieceRequester.cs index a7adb7e39..071fd9a1b 100644 --- a/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/StandardPieceRequester.cs +++ b/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/StandardPieceRequester.cs @@ -36,9 +36,11 @@ namespace MonoTorrent.PiecePicking public class StandardPieceRequester : IPieceRequester { IReadOnlyList? IgnorableBitfields { get; set; } - Memory BlockInfoBufferCache { get; set; } Memory RequestBufferCache { get; set; } + Memory OtherBitfieldCache { get; set; } BitField? Temp { get; set; } + + IMessageEnqueuer? Enqueuer { get; set; } IPieceRequesterData? TorrentData { get; set; } public bool InEndgameMode { get; private set; } @@ -48,14 +50,15 @@ public class StandardPieceRequester : IPieceRequester public StandardPieceRequester (PieceRequesterSettings settings) => Settings = settings ?? throw new ArgumentNullException (nameof (settings)); - public void Initialise (IPieceRequesterData torrentData, IReadOnlyList ignoringBitfields) + public void Initialise (IPieceRequesterData torrentData, IMessageEnqueuer enqueuer, ReadOnlySpan ignoringBitfields) { - IgnorableBitfields = ignoringBitfields; + IgnorableBitfields = ignoringBitfields.ToArray (); + Enqueuer = enqueuer; TorrentData = torrentData; Temp = new BitField (TorrentData.PieceCount); - IPiecePicker picker = new StandardPicker (); + IPiecePicker picker = new StandardPicker (Settings.IgnoreBitFieldAndChokeState); if (Settings.AllowRandomised) picker = new RandomisedPicker (picker); if (Settings.AllowRarestFirst) @@ -75,30 +78,37 @@ ReadOnlyBitField ApplyIgnorables (ReadOnlyBitField primary) return Temp; } - public void AddRequests (IReadOnlyList peers) + ReadOnlyBitField[] otherAvailableCache = Array.Empty (); + public void AddRequests (ReadOnlySpan<(IPeer Peer, ReadOnlyBitField Available)> peers) { - for (int i = 0; i < peers.Count; i++) { - var peer = peers[i]; + if (otherAvailableCache.Length < peers.Length) + otherAvailableCache = new ReadOnlyBitField[peers.Length]; + var otherAvailable = otherAvailableCache.AsSpan (0, peers.Length); + for (int i = 0; i < otherAvailable.Length; i++) + otherAvailable[i] = peers[i].Available; + + for (int i = 0; i < peers.Length; i++) { + (var peer, var bitfield) = peers[i]; if (peer.SuggestedPieces.Count > 0 || (!peer.IsChoking && peer.AmRequestingPiecesCount == 0)) - AddRequests (peer, peers); + AddRequests (peer, bitfield, otherAvailable); } } - public void AddRequests (IPeerWithMessaging peer, IReadOnlyList allPeers) + public void AddRequests (IPeer peer, ReadOnlyBitField available, ReadOnlySpan allPeers) { int maxRequests = peer.MaxPendingRequests; - if (!peer.CanRequestMorePieces || Picker == null || TorrentData == null) + if (!peer.CanRequestMorePieces || Picker == null || TorrentData == null || Enqueuer == null) return; // This is safe to invoke. 'ContinueExistingRequest' strongly guarantees that a peer will only // continue a piece they have initiated. If they're choking then the only piece they can continue // will be a fast piece (if one exists!) - if (!peer.IsChoking || peer.SupportsFastPeer) { + if (Settings.IgnoreBitFieldAndChokeState || !peer.IsChoking || peer.SupportsFastPeer) { while (peer.AmRequestingPiecesCount < maxRequests) { - PieceSegment? request = Picker.ContinueExistingRequest (peer, 0, peer.BitField.Length - 1); + PieceSegment? request = Picker.ContinueExistingRequest (peer, 0, available.Length - 1); if (request != null) - peer.EnqueueRequest (request.Value.ToBlockInfo (TorrentData)); + Enqueuer.EnqueueRequest (peer, request.Value); else break; } @@ -107,86 +117,79 @@ public void AddRequests (IPeerWithMessaging peer, IReadOnlyList (new PieceSegment[count]); - if (BlockInfoBufferCache.Length < count) - BlockInfoBufferCache = new Memory (new BlockInfo[count]); + // Reuse the same buffer across multiple requests. However ensure the piecepicker is given // a Span of the expected size - so slice the reused buffer if it's too large. var requestBuffer = RequestBufferCache.Span.Slice (0, count); - if (!peer.IsChoking || (peer.SupportsFastPeer && peer.IsAllowedFastPieces.Count > 0)) { + if (Settings.IgnoreBitFieldAndChokeState || !peer.IsChoking || (peer.SupportsFastPeer && peer.IsAllowedFastPieces.Count > 0)) { ReadOnlyBitField filtered = null!; + while (peer.AmRequestingPiecesCount < maxRequests) { - filtered ??= ApplyIgnorables (peer.BitField); + if (Settings.IgnoreBitFieldAndChokeState) + filtered ??= ApplyIgnorables (Temp!.SetAll (true)); + else + filtered ??= ApplyIgnorables (available); + int requests = Picker.PickPiece (peer, filtered, allPeers, 0, TorrentData.PieceCount - 1, requestBuffer); if (requests > 0) - peer.EnqueueRequests (requestBuffer.Slice (0, requests).ToBlockInfo (BlockInfoBufferCache.Span, TorrentData)); + Enqueuer.EnqueueRequests (peer, requestBuffer.Slice (0, requests)); else break; } } - if (!peer.IsChoking && peer.AmRequestingPiecesCount == 0) { + if ((Settings.IgnoreBitFieldAndChokeState || !peer.IsChoking) && peer.AmRequestingPiecesCount == 0) { + ReadOnlyBitField filtered = null!; while (peer.AmRequestingPiecesCount < maxRequests) { - PieceSegment? request = Picker.ContinueAnyExistingRequest (peer, 0, TorrentData.PieceCount - 1, 1); + if (Settings.IgnoreBitFieldAndChokeState) + filtered ??= ApplyIgnorables (Temp!.SetAll (true)); + else + filtered ??= ApplyIgnorables (available); + PieceSegment? request = Picker.ContinueAnyExistingRequest (peer, filtered, 0, TorrentData.PieceCount - 1, 1); // If this peer is a seeder and we are unable to request any new blocks, then we should enter // endgame mode. Every block has been requested at least once at this point. - if (request == null && (InEndgameMode || peer.IsSeeder)) { - request = Picker.ContinueAnyExistingRequest (peer, 0, TorrentData.PieceCount - 1, 2); + if (request == null && (InEndgameMode || available.AllTrue)) { + request = Picker.ContinueAnyExistingRequest (peer, filtered, 0, TorrentData.PieceCount - 1, 2); InEndgameMode |= request != null; } if (request != null) - peer.EnqueueRequest (request.Value.ToBlockInfo (TorrentData)); + Enqueuer.EnqueueRequest (peer, request.Value); else break; } } } - public bool ValidatePiece (IPeer peer, BlockInfo blockInfo, out bool pieceComplete, out IList peersInvolved) + public bool ValidatePiece (IPeer peer, PieceSegment blockInfo, out bool pieceComplete, out IList peersInvolved) { pieceComplete = false; peersInvolved = Array.Empty (); - return Picker != null && Picker.ValidatePiece (peer, blockInfo.ToPieceSegment (), out pieceComplete, out peersInvolved); + return Picker != null && Picker.ValidatePiece (peer, blockInfo, out pieceComplete, out peersInvolved); } public bool IsInteresting (IPeer peer, ReadOnlyBitField bitfield) => Picker != null && Picker.IsInteresting (peer, bitfield); - public IList CancelRequests (IPeer peer, int startIndex, int endIndex) - => Picker == null ? Array.Empty () : Picker.CancelRequests (peer, startIndex, endIndex).Select (t => t.ToBlockInfo (TorrentData!)).ToArray (); - - public void RequestRejected (IPeer peer, BlockInfo pieceRequest) - => Picker?.RequestRejected (peer, pieceRequest.ToPieceSegment ()); - - public int CurrentRequestCount () - => Picker == null ? 0 : Picker.CurrentRequestCount (); - - } + PieceSegment[] CancellationsCache = Array.Empty (); + public void CancelRequests (IPeer peer, int startIndex, int endIndex) + { + if (Picker == null || Enqueuer == null) + return; - static class PieceRequesterExtensions - { - public static PieceSegment ToPieceSegment (this BlockInfo blockInfo) - => new PieceSegment (blockInfo.PieceIndex, blockInfo.StartOffset / Constants.BlockSize); + if (CancellationsCache.Length < peer.AmRequestingPiecesCount) + CancellationsCache = new PieceSegment[peer.AmRequestingPiecesCount]; - public static BlockInfo ToBlockInfo (this PieceSegment pieceSegment, IPieceRequesterData info) - { - var totalBlocks = info.BlocksPerPiece (pieceSegment.PieceIndex); - var size = pieceSegment.BlockIndex == totalBlocks - 1 ? info.BytesPerPiece (pieceSegment.PieceIndex) - (pieceSegment.BlockIndex) * Constants.BlockSize : Constants.BlockSize; - return new BlockInfo (pieceSegment.PieceIndex, pieceSegment.BlockIndex * Constants.BlockSize, size); + var cancellations = CancellationsCache.AsSpan (0, peer.AmRequestingPiecesCount); + var cancelled = Picker.CancelRequests (peer, startIndex, endIndex, cancellations); + Enqueuer.EnqueueCancellations (peer, cancellations.Slice (0, cancelled)); } - public static Span ToBlockInfo (this Span segments, Span blocks, IPieceRequesterData info) - { - for (int i = 0; i < segments.Length; i++) - blocks[i] = segments[i].ToBlockInfo (info); - return blocks.Slice (0, segments.Length); - } + public void RequestRejected (IPeer peer, PieceSegment pieceRequest) + => Picker?.RequestRejected (peer, pieceRequest); + + public int CurrentRequestCount () + => Picker == null ? 0 : Picker.CurrentRequestCount (); - public static Span ToPieceSegment (this Span blocks, Span segments) - { - for (int i = 0; i < segments.Length; i++) - segments[i] = blocks[i].ToPieceSegment (); - return segments.Slice (0, segments.Length); - } } } diff --git a/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/StreamingPieceRequester.cs b/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/StreamingPieceRequester.cs index 18c49013b..b4d43cf5d 100644 --- a/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/StreamingPieceRequester.cs +++ b/src/MonoTorrent.PiecePicking/MonoTorrent.PiecePicking/StreamingPieceRequester.cs @@ -37,6 +37,7 @@ public class StreamingPieceRequester : IStreamingPieceRequester { bool RefreshAfterSeeking = false; + IMessageEnqueuer? Enqueuer { get; set; } IPieceRequesterData? TorrentData { get; set; } IReadOnlyList? IgnoringBitfields { get; set; } @@ -62,33 +63,36 @@ public class StreamingPieceRequester : IStreamingPieceRequester BitField? Temp { get; set; } - public void Initialise (IPieceRequesterData torrentData, IReadOnlyList ignoringBitfields) + public void Initialise (IPieceRequesterData torrentData, IMessageEnqueuer enqueuer, ReadOnlySpan ignoringBitfields) { TorrentData = torrentData; - IgnoringBitfields = ignoringBitfields; + Enqueuer = enqueuer; + IgnoringBitfields = ignoringBitfields.ToArray (); Temp = new BitField (TorrentData.PieceCount); var standardPicker = new StandardPicker (); - HighPriorityPicker = IgnoringPicker.Wrap (new PriorityPicker (standardPicker), ignoringBitfields); + HighPriorityPicker = IgnoringPicker.Wrap (new PriorityPicker (standardPicker), IgnoringBitfields); LowPriorityPicker = new RandomisedPicker (standardPicker); LowPriorityPicker = new RarestFirstPicker (LowPriorityPicker); LowPriorityPicker = new PriorityPicker (LowPriorityPicker); - LowPriorityPicker = IgnoringPicker.Wrap (LowPriorityPicker, ignoringBitfields); + LowPriorityPicker = IgnoringPicker.Wrap (LowPriorityPicker, IgnoringBitfields); LowPriorityPicker.Initialise (torrentData); HighPriorityPicker.Initialise (torrentData); } - public void AddRequests (IReadOnlyList peers) + ReadOnlyBitField[] otherAvailableCache = Array.Empty (); + public void AddRequests (ReadOnlySpan<(IPeer Peer, ReadOnlyBitField Available)> peers) { - if (!RefreshAfterSeeking || TorrentData is null) + if (!RefreshAfterSeeking || TorrentData is null || Enqueuer == null) return; RefreshAfterSeeking = false; var allPeers = peers - .OrderBy (t => -t.DownloadSpeed) + .ToArray () + .OrderBy (t => -t.Peer.DownloadSpeed) .ToArray (); // It's only worth cancelling requests for peers which support cancellation. This is part of @@ -99,17 +103,23 @@ public void AddRequests (IReadOnlyList peers) var bitfield = GenerateAlreadyHaves (); if (bitfield.FirstFalse (start, end) != -1) { - foreach (var peer in allPeers.Where (p => p.CanCancelRequests)) { + foreach (var peer in allPeers.Where (p => p.Peer.CanCancelRequests)) { if (HighPriorityPieceIndex > 0) - peer.EnqueueCancellations (HighPriorityPicker!.CancelRequests (peer, 0, HighPriorityPieceIndex - 1).Select (t => t.ToBlockInfo (TorrentData)).ToArray ()); + CancelRequests (peer.Peer, 0, HighPriorityPieceIndex - 1); if (HighPriorityPieceIndex + HighPriorityCount < bitfield.Length) - peer.EnqueueCancellations (HighPriorityPicker!.CancelRequests (peer, HighPriorityPieceIndex + HighPriorityCount, bitfield.Length - 1).Select (t => t.ToBlockInfo (TorrentData)).ToArray ()); + CancelRequests (peer.Peer, HighPriorityPieceIndex + HighPriorityCount, bitfield.Length - 1); } } + if (otherAvailableCache.Length < peers.Length) + otherAvailableCache = new ReadOnlyBitField[peers.Length]; + var otherAvailable = otherAvailableCache.AsSpan (0, peers.Length); + for (int i = 0; i < otherAvailable.Length; i++) + otherAvailable[i] = peers[i].Available; + var fastestPeers = allPeers - .Where (t => t.DownloadSpeed > 50 * 1024) + .Where (t => t.Peer.DownloadSpeed > 50 * 1024) .ToArray (); // Queue up 12 pieces for each of our fastest peers. At a download @@ -118,18 +128,18 @@ public void AddRequests (IReadOnlyList peers) // is likely to be empty. foreach (var supportsFastPeer in new[] { true, false }) { for (int i = 0; i < 4; i++) { - foreach (var peer in fastestPeers.Where (p => p.SupportsFastPeer == supportsFastPeer)) { - AddRequests (peer, peers, HighPriorityPieceIndex, Math.Min (HighPriorityPieceIndex + 1, bitfield.Length - 1), 2, preferredMaxRequests: (i + 1) * 2); + foreach (var peer in fastestPeers.Where (p => p.Peer.SupportsFastPeer == supportsFastPeer)) { + AddRequests (peer.Peer, peer.Available, otherAvailable, HighPriorityPieceIndex, Math.Min (HighPriorityPieceIndex + 1, bitfield.Length - 1), 2, preferredMaxRequests: (i + 1) * 2); } } } // Then fill up the request queues for all peers foreach (var peer in peers) - AddRequests (peer, peers); + AddRequests (peer.Peer, peer.Available, otherAvailable); } - public void AddRequests (IPeerWithMessaging peer, IReadOnlyList allPeers) + public void AddRequests (IPeer peer, ReadOnlyBitField available, ReadOnlySpan allPeers) { if (TorrentData is null) return; @@ -137,16 +147,16 @@ public void AddRequests (IPeerWithMessaging peer, IReadOnlyList allPeers, int startPieceIndex, int endPieceIndex, int maxDuplicates, int preferredMaxRequests) + void AddRequests (IPeer peer, ReadOnlyBitField available, ReadOnlySpan allPeers, int startPieceIndex, int endPieceIndex, int maxDuplicates, int preferredMaxRequests) { - if (!peer.CanRequestMorePieces || TorrentData == null) + if (!peer.CanRequestMorePieces || TorrentData == null || Enqueuer == null) return; int preferredRequestAmount = peer.PreferredRequestAmount (TorrentData.PieceLength); @@ -161,9 +171,9 @@ void AddRequests (IPeerWithMessaging peer, IReadOnlyList all // into account that a peer which is choking us can *only* resume a 'fast piece' in the 'AmAllowedfastPiece' list. if (!peer.IsChoking) { while (peer.AmRequestingPiecesCount < maxRequests) { - PieceSegment? request = LowPriorityPicker!.ContinueAnyExistingRequest (peer, startPieceIndex, endPieceIndex, maxDuplicates); + PieceSegment? request = LowPriorityPicker!.ContinueAnyExistingRequest (peer, available, startPieceIndex, endPieceIndex, maxDuplicates); if (request != null) - peer.EnqueueRequest (request.Value.ToBlockInfo (TorrentData)); + Enqueuer.EnqueueRequest(peer, request.Value); else break; } @@ -174,7 +184,7 @@ void AddRequests (IPeerWithMessaging peer, IReadOnlyList all while (peer.AmRequestingPiecesCount < maxRequests) { PieceSegment? request = LowPriorityPicker!.ContinueExistingRequest (peer, startPieceIndex, endPieceIndex); if (request != null) - peer.EnqueueRequest (request.Value.ToBlockInfo (TorrentData)); + Enqueuer.EnqueueRequest (peer, request.Value); else break; } @@ -186,14 +196,11 @@ void AddRequests (IPeerWithMessaging peer, IReadOnlyList all if (!peer.IsChoking || (peer.SupportsFastPeer && peer.IsAllowedFastPieces.Count > 0)) { BitField filtered = null!; while (peer.AmRequestingPiecesCount < maxRequests) { - filtered ??= GenerateAlreadyHaves ().Not ().And (peer.BitField); - + filtered ??= GenerateAlreadyHaves ().Not ().And (available); Span buffer = stackalloc PieceSegment[preferredRequestAmount]; int requested = PriorityPick (peer, filtered, allPeers, startPieceIndex, endPieceIndex, buffer); if (requested > 0) { - Span blocks = stackalloc BlockInfo[requested]; - buffer.Slice (0, requested).ToBlockInfo (blocks, TorrentData); - peer.EnqueueRequests (blocks); + Enqueuer.EnqueueRequests (peer, buffer.Slice (0, requested)); } else break; } @@ -208,7 +215,7 @@ BitField GenerateAlreadyHaves () return Temp; } - int PriorityPick (IPeer peer, ReadOnlyBitField available, IReadOnlyList otherPeers, int startIndex, int endIndex, Span requests) + int PriorityPick (IPeer peer, ReadOnlyBitField available, ReadOnlySpan otherPeers, int startIndex, int endIndex, Span requests) { PieceSegment? request; int requestCount; @@ -221,7 +228,7 @@ int PriorityPick (IPeer peer, ReadOnlyBitField available, IReadOnlyList o if (available[prioritised]) { if ((requestCount = HighPriorityPicker!.PickPiece (peer, available, otherPeers, prioritised, prioritised, requests)) > 0) return requestCount; - if ((request = HighPriorityPicker.ContinueAnyExistingRequest (peer, prioritised, prioritised, 3)) != null) { + if ((request = HighPriorityPicker.ContinueAnyExistingRequest (peer, available, prioritised, prioritised, 3)) != null) { requests[0] = request.Value; return 1; } @@ -269,17 +276,28 @@ public void ReadToPosition (ITorrentManagerFile file, long position) HighPriorityPieceIndex = Math.Min (file.EndPieceIndex, TorrentData.ByteOffsetToPieceIndex (position + file.OffsetInTorrent)); } - public bool ValidatePiece (IPeer peer, BlockInfo blockInfo, out bool pieceComplete, out IList peersInvolved) - => HighPriorityPicker!.ValidatePiece (peer, blockInfo.ToPieceSegment (), out pieceComplete, out peersInvolved); + public bool ValidatePiece (IPeer peer, PieceSegment blockInfo, out bool pieceComplete, out IList peersInvolved) + => HighPriorityPicker!.ValidatePiece (peer, blockInfo, out pieceComplete, out peersInvolved); public bool IsInteresting (IPeer peer, ReadOnlyBitField bitfield) => HighPriorityPicker!.IsInteresting (peer, bitfield); - public IList CancelRequests (IPeer peer, int startIndex, int endIndex) - => HighPriorityPicker!.CancelRequests (peer, startIndex, endIndex).Select (t => t.ToBlockInfo (TorrentData!)).ToArray (); + PieceSegment[] CancellationsCache = Array.Empty (); + public void CancelRequests (IPeer peer, int startIndex, int endIndex) + { + if (HighPriorityPicker is null || Enqueuer is null) + return; + + if (CancellationsCache.Length < peer.AmRequestingPiecesCount) + CancellationsCache = new PieceSegment[peer.AmRequestingPiecesCount]; + + var cancellations = CancellationsCache.AsSpan (0, peer.AmRequestingPiecesCount); + var cancelled = HighPriorityPicker.CancelRequests (peer, startIndex, endIndex, cancellations); + Enqueuer.EnqueueCancellations (peer, cancellations.Slice (0, cancelled)); + } - public void RequestRejected (IPeer peer, BlockInfo blockInfo) - => HighPriorityPicker!.RequestRejected (peer, blockInfo.ToPieceSegment ()); + public void RequestRejected (IPeer peer, PieceSegment blockInfo) + => HighPriorityPicker!.RequestRejected (peer, blockInfo); public int CurrentRequestCount () => HighPriorityPicker!.CurrentRequestCount (); diff --git a/src/MonoTorrent/MonoTorrent.PiecePicking/IPeer.cs b/src/MonoTorrent/MonoTorrent.PiecePicking/IPeer.cs index 0c3d2672b..5e6a5f668 100644 --- a/src/MonoTorrent/MonoTorrent.PiecePicking/IPeer.cs +++ b/src/MonoTorrent/MonoTorrent.PiecePicking/IPeer.cs @@ -35,17 +35,14 @@ namespace MonoTorrent.PiecePicking public interface IPeer { int AmRequestingPiecesCount { get; set; } - ReadOnlyBitField BitField { get; } bool CanRequestMorePieces { get; } long DownloadSpeed { get; } List IsAllowedFastPieces { get; } bool IsChoking { get; } - bool IsSeeder { get; } int MaxPendingRequests { get; } int RepeatedHashFails { get; } List SuggestedPieces { get; } bool SupportsFastPeer { get; } - int TotalHashFails { get; } // FIXME: We need to support 3rd party implementations of 'IPiecePicker' calling // CancelRequest if the FastPeer extensions are supported. This includes enqueuing @@ -60,12 +57,4 @@ public interface IPeer /// int PreferredRequestAmount (int pieceLength); } - - public interface IPeerWithMessaging : IPeer - { - void EnqueueRequest (BlockInfo request); - void EnqueueRequests (Span requests); - void EnqueueCancellation (BlockInfo request); - void EnqueueCancellations (IList requests); - } } diff --git a/src/MonoTorrent/MonoTorrent.PiecePicking/IPieceRequester.cs b/src/MonoTorrent/MonoTorrent.PiecePicking/IPieceRequester.cs index a6840700f..5efa00475 100644 --- a/src/MonoTorrent/MonoTorrent.PiecePicking/IPieceRequester.cs +++ b/src/MonoTorrent/MonoTorrent.PiecePicking/IPieceRequester.cs @@ -27,6 +27,7 @@ // +using System; using System.Collections.Generic; namespace MonoTorrent.PiecePicking @@ -34,13 +35,20 @@ namespace MonoTorrent.PiecePicking public interface IPieceRequesterData { IList Files { get; } - int BlocksPerPiece (int piece); + int SegmentsPerPiece (int piece); int ByteOffsetToPieceIndex (long byteOffset); int BytesPerPiece (int piece); int PieceCount { get; } int PieceLength { get; } } + public interface IMessageEnqueuer + { + void EnqueueRequest (IPeer peer, PieceSegment block); + void EnqueueRequests (IPeer peer, Span blocks); + void EnqueueCancellation (IPeer peer, PieceSegment segment); + void EnqueueCancellations (IPeer peer, Span segments); + } /// /// Allows an IPiecePicker implementation to create piece requests for @@ -59,24 +67,25 @@ public interface IPieceRequester /// Should enqueue piece requests for any peer who is has capacity. /// /// - void AddRequests (IReadOnlyList peers); + void AddRequests (ReadOnlySpan<(IPeer Peer, ReadOnlyBitField Available)> peers); /// /// Attempts to enqueue more requests for the specified peer. /// /// + /// /// - void AddRequests (IPeerWithMessaging peer, IReadOnlyList peers); + void AddRequests (IPeer peer, ReadOnlyBitField available, ReadOnlySpan peers); /// /// /// /// - /// + /// /// /// /// - bool ValidatePiece (IPeer peer, BlockInfo blockInfo, out bool pieceComplete, out IList peersInvolved); + bool ValidatePiece (IPeer peer, PieceSegment pieceSegment, out bool pieceComplete, out IList peersInvolved); /// /// @@ -90,15 +99,16 @@ public interface IPieceRequester /// /// /// The files, size and piecelength for the torrent. + /// Enqueues request, or cancellation, messages with the peer /// These bitfields represent pieces which have successfully /// downloaded and passed a hash check, pieces which have successfully downloaded but have not hash checked yet or /// pieces which have not yet been hash checked by the library and so it is not known whether they should be requested or not. /// - void Initialise (IPieceRequesterData torrentData, IReadOnlyList ignorableBitfields); + void Initialise (IPieceRequesterData torrentData, IMessageEnqueuer enqueuer, ReadOnlySpan ignorableBitfields); - IList CancelRequests (IPeer peer, int startIndex, int endIndex); + void CancelRequests (IPeer peer, int startIndex, int endIndex); - void RequestRejected (IPeer peer, BlockInfo pieceRequest); + void RequestRejected (IPeer peer, PieceSegment pieceRequest); int CurrentRequestCount (); } diff --git a/src/MonoTorrent/MonoTorrent.PiecePicking/PieceRequesterExtensions.cs b/src/MonoTorrent/MonoTorrent.PiecePicking/PieceRequesterExtensions.cs new file mode 100644 index 000000000..2bf001242 --- /dev/null +++ b/src/MonoTorrent/MonoTorrent.PiecePicking/PieceRequesterExtensions.cs @@ -0,0 +1,60 @@ +// +// StandardPieceRequester.cs +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2021 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + + +using System; + +namespace MonoTorrent.PiecePicking +{ + public static class PieceRequesterExtensions + { + public static PieceSegment ToPieceSegment (this BlockInfo blockInfo) + => new PieceSegment (blockInfo.PieceIndex, blockInfo.StartOffset / Constants.BlockSize); + + public static BlockInfo ToBlockInfo (this PieceSegment pieceSegment, IPieceRequesterData info) + { + var totalBlocks = info.SegmentsPerPiece (pieceSegment.PieceIndex); + var size = pieceSegment.BlockIndex == totalBlocks - 1 ? info.BytesPerPiece (pieceSegment.PieceIndex) - (pieceSegment.BlockIndex) * Constants.BlockSize : Constants.BlockSize; + return new BlockInfo (pieceSegment.PieceIndex, pieceSegment.BlockIndex * Constants.BlockSize, size); + } + + public static Span ToBlockInfo (this Span segments, Span blocks, IPieceRequesterData info) + { + for (int i = 0; i < segments.Length; i++) + blocks[i] = segments[i].ToBlockInfo (info); + return blocks.Slice (0, segments.Length); + } + + public static Span ToPieceSegment (this Span blocks, Span segments) + { + for (int i = 0; i < segments.Length; i++) + segments[i] = blocks[i].ToPieceSegment (); + return segments.Slice (0, segments.Length); + } + } +} diff --git a/src/MonoTorrent.PiecePicking/PieceRequesterSettings.cs b/src/MonoTorrent/MonoTorrent.PiecePicking/PieceRequesterSettings.cs similarity index 52% rename from src/MonoTorrent.PiecePicking/PieceRequesterSettings.cs rename to src/MonoTorrent/MonoTorrent.PiecePicking/PieceRequesterSettings.cs index 607d52334..7bf05ddbc 100644 --- a/src/MonoTorrent.PiecePicking/PieceRequesterSettings.cs +++ b/src/MonoTorrent/MonoTorrent.PiecePicking/PieceRequesterSettings.cs @@ -32,14 +32,37 @@ public class PieceRequesterSettings { public static PieceRequesterSettings Default { get; } = new PieceRequesterSettings (); + /// + /// When set to false, is not taken into account when choosing pieces. Files marked as 'DoNotDownload' will be downloaded. + /// Defaults to . + /// public bool AllowPrioritisation { get; } + + /// + /// When set to false, pieces will be selected sequentially. If is enabled, then set of pieces which will be available to choose from will be reduced to the 'rarest' set first, + /// and then the picker will choose sequentially from that subset. If you need true linear picking, you must disable as well as . + /// Defaults to . + /// public bool AllowRandomised { get; } + + + /// + /// When set to false, the rarest subset of pieces will not be computed. + /// Defaults to . + /// public bool AllowRarestFirst { get; } + /// + /// When set to true, the bitfield from the requesting peer, and their choke/unchoke state, will not be taken into account. This is useful when creating a to retrieve things + /// like the torrent metadata, or the bittorrent v2 hashes, from peers. + /// + public bool IgnoreBitFieldAndChokeState { get; } + public PieceRequesterSettings ( bool allowPrioritisation = true, bool allowRandomised = true, - bool allowRarestFirst = true) - => (AllowPrioritisation, AllowRandomised, AllowRarestFirst) = (allowPrioritisation, allowRandomised, allowRarestFirst); + bool allowRarestFirst = true, + bool ignoreBitFieldAndChokeState = false) + => (AllowPrioritisation, AllowRandomised, AllowRarestFirst, IgnoreBitFieldAndChokeState) = (allowPrioritisation, allowRandomised, allowRarestFirst, ignoreBitFieldAndChokeState); } } diff --git a/src/MonoTorrent/MonoTorrent/ITorrentInfo.cs b/src/MonoTorrent/MonoTorrent/ITorrentInfo.cs index 3e22870be..b88a45e06 100644 --- a/src/MonoTorrent/MonoTorrent/ITorrentInfo.cs +++ b/src/MonoTorrent/MonoTorrent/ITorrentInfo.cs @@ -77,6 +77,9 @@ static bool IsHybrid (this ITorrentInfo self) public static int BlocksPerPiece (this ITorrentInfo self, int pieceIndex) => (BytesPerPiece (self, pieceIndex) + Constants.BlockSize - 1) / Constants.BlockSize; + public static int BytesPerBlock (this ITorrentInfo self, int pieceIndex, int blockIndex) + => Math.Min (Constants.BlockSize, self.BytesPerPiece (pieceIndex) - blockIndex * Constants.BlockSize); + public static int BytesPerPiece (this ITorrentInfo self, int pieceIndex) { if (self.IsV1Only ()) { diff --git a/src/Samples/SampleClient/StandardDownloader.cs b/src/Samples/SampleClient/StandardDownloader.cs index 9112606e3..8a321a365 100644 --- a/src/Samples/SampleClient/StandardDownloader.cs +++ b/src/Samples/SampleClient/StandardDownloader.cs @@ -52,10 +52,7 @@ public async Task DownloadAsync (CancellationToken token) MaximumConnections = 60, }; var torrent = await Torrent.LoadAsync (file); - if (torrent.InfoHashes.V2 == null) - continue; - - var manager = await Engine.AddAsync (new MagnetLink (torrent.InfoHashes.V2, torrent.Name, torrent.AnnounceUrls.SelectMany (t => t).ToArray ()), downloadsPath, settingsBuilder.ToSettings ()); + var manager = await Engine.AddAsync (torrent, downloadsPath, settingsBuilder.ToSettings ()); manager.PeersFound += Manager_PeersFound; Console.WriteLine (manager.InfoHashes.V1OrV2.ToHex ()); } catch (Exception e) { diff --git a/src/Tests/Tests.MonoTorrent.Client/MonoTorrent.Client.Modes/MetadataModeTests.2.cs b/src/Tests/Tests.MonoTorrent.Client/MonoTorrent.Client.Modes/MetadataModeTests.2.cs new file mode 100644 index 000000000..daa597f4d --- /dev/null +++ b/src/Tests/Tests.MonoTorrent.Client/MonoTorrent.Client.Modes/MetadataModeTests.2.cs @@ -0,0 +1,118 @@ +// +// MetadataModeTest.cs +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2019 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; + +using MonoTorrent.Connections.Peer.Encryption; +using MonoTorrent.Messages.Peer; +using MonoTorrent.Messages.Peer.FastPeer; +using MonoTorrent.Messages.Peer.Libtorrent; + +using NUnit.Framework; + +namespace MonoTorrent.Client.Modes +{ + [TestFixture] + public class MetadataModeTests2 + { + string HybridTorrentPath => Path.Combine (Path.GetDirectoryName (typeof (MetadataModeTests2).Assembly.Location), "MonoTorrent", "bittorrent-v2-hybrid-test.torrent"); + string V2OnlyTorrentPath => Path.Combine (Path.GetDirectoryName (typeof (MetadataModeTests2).Assembly.Location), "MonoTorrent", "bittorrent-v2-test.torrent"); + + [Test] + public async Task RequestMetadata () + { + var engine = new ClientEngine (EngineSettingsBuilder.CreateForTests (autoSaveLoadMagnetLinkMetadata: false)); + var torrent = await Torrent.LoadAsync (HybridTorrentPath); + var manager = await engine.AddAsync (new MagnetLink (torrent.InfoHashes), "bbb"); + var metadataMode = new MetadataMode (manager, engine.DiskManager, engine.ConnectionManager, engine.Settings, "blarp", true); + var peer = manager.AddConnectedPeer (supportsLTMetdata: true); + + metadataMode.HandleMessage (peer, new ExtendedHandshakeMessage (false, torrent.InfoMetadata.Length, 12345), default); + while (manager.Torrent is null) { + metadataMode.Tick (0); + PeerMessage message; + while ((message = peer.MessageQueue.TryDequeue ()) != null) { + if (message is LTMetadata metadata) { + if (metadata.MetadataMessageType == LTMetadata.MessageType.Request) { + var data = torrent.InfoMetadata.Slice (metadata.Piece * Constants.BlockSize); + data = data.Slice (0, Math.Min (Constants.BlockSize, data.Length)); + metadataMode.HandleMessage (peer, new LTMetadata (LTMetadata.Support.MessageId, LTMetadata.MessageType.Data, metadata.Piece, data), default); +; } + + } + } + } + + Assert.AreEqual (manager.Torrent.InfoHashes, manager.InfoHashes); + } + + [Test] + public async Task RequestMetadata_OnePeerDisconnects () + { + var engine = new ClientEngine (EngineSettingsBuilder.CreateForTests (autoSaveLoadMagnetLinkMetadata: false)); + var torrent = await Torrent.LoadAsync (HybridTorrentPath); + var manager = await engine.AddAsync (new MagnetLink (torrent.InfoHashes), "bbb"); + var metadataMode = new MetadataMode (manager, engine.DiskManager, engine.ConnectionManager, engine.Settings, "blarp", true); + var peer = manager.AddConnectedPeer (supportsLTMetdata: true); + + metadataMode.HandleMessage (peer, new ExtendedHandshakeMessage (false, torrent.InfoMetadata.Length, 12345), default); + Assert.AreNotEqual (0, peer.AmRequestingPiecesCount); + + engine.ConnectionManager.CleanupSocket (manager, peer); + metadataMode.HandlePeerDisconnected (peer); + Assert.AreEqual (0, peer.AmRequestingPiecesCount); + + peer = manager.AddConnectedPeer (supportsLTMetdata: true); + metadataMode.HandleMessage (peer, new ExtendedHandshakeMessage (false, torrent.InfoMetadata.Length, 12345), default); + + while (manager.Torrent is null) { + metadataMode.Tick (0); + PeerMessage message; + while ((message = peer.MessageQueue.TryDequeue ()) != null) { + if (message is LTMetadata metadata) { + if (metadata.MetadataMessageType == LTMetadata.MessageType.Request) { + var data = torrent.InfoMetadata.Slice (metadata.Piece * Constants.BlockSize); + data = data.Slice (0, Math.Min (Constants.BlockSize, data.Length)); + metadataMode.HandleMessage (peer, new LTMetadata (LTMetadata.Support.MessageId, LTMetadata.MessageType.Data, metadata.Piece, data), default); + } + } + } + } + + Assert.AreEqual (manager.Torrent.InfoHashes, manager.InfoHashes); + } + } +} diff --git a/src/Tests/Tests.MonoTorrent.Client/MonoTorrent.Client.Modes/MetadataModeTests.cs b/src/Tests/Tests.MonoTorrent.Client/MonoTorrent.Client.Modes/MetadataModeTests.cs index 101f84e71..a48ce5b32 100644 --- a/src/Tests/Tests.MonoTorrent.Client/MonoTorrent.Client.Modes/MetadataModeTests.cs +++ b/src/Tests/Tests.MonoTorrent.Client/MonoTorrent.Client.Modes/MetadataModeTests.cs @@ -116,7 +116,7 @@ public async Task RequestMetadata () while (length > 0 && (m = await PeerIO.ReceiveMessageAsync (connection, decryptor)) != null) { if (m is LTMetadata metadata) { if (metadata.MetadataMessageType == LTMetadata.MessageType.Data) { - stream.Write (metadata.MetadataPiece, 0, metadata.MetadataPiece.Length); + stream.Write (metadata.MetadataPiece); length--; } } @@ -295,7 +295,7 @@ internal async Task SendMetadataCore (string expectedPath, PeerMessage sendAfter bool receivedHaveNone = false; // 2) Receive the metadata requests from the other peer and fulfill them - byte[] buffer = rig.Torrent.InfoMetadata; + ReadOnlyMemory buffer = rig.Torrent.InfoMetadata; var unrequestedPieces = new HashSet (Enumerable.Range (0, (buffer.Length + 16383) / 16384)); PeerMessage m; while (unrequestedPieces.Count > 0 && (m = await PeerIO.ReceiveMessageAsync (connection, decryptor)) != null) { diff --git a/src/Tests/Tests.MonoTorrent.Client/MonoTorrent.Client.Modes/PieceHashesModeTests.cs b/src/Tests/Tests.MonoTorrent.Client/MonoTorrent.Client.Modes/PieceHashesModeTests.cs new file mode 100644 index 000000000..671f11247 --- /dev/null +++ b/src/Tests/Tests.MonoTorrent.Client/MonoTorrent.Client.Modes/PieceHashesModeTests.cs @@ -0,0 +1,143 @@ +// +// MetadataModeTest.cs +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2019 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; + +using MonoTorrent.BEncoding; +using MonoTorrent.Connections.Peer.Encryption; +using MonoTorrent.Messages.Peer; +using MonoTorrent.Messages.Peer.FastPeer; +using MonoTorrent.Messages.Peer.Libtorrent; + +using NUnit.Framework; + +namespace MonoTorrent.Client.Modes +{ + [TestFixture] + public class PieceHashesModeTests + { + string HybridTorrentPath => Path.Combine (Path.GetDirectoryName (typeof (MetadataModeTests2).Assembly.Location), "MonoTorrent", "bittorrent-v2-hybrid-test.torrent"); + string V2OnlyTorrentPath => Path.Combine (Path.GetDirectoryName (typeof (MetadataModeTests2).Assembly.Location), "MonoTorrent", "bittorrent-v2-test.torrent"); + + async Task<(ClientEngine engine, TorrentManager manager, PieceHashesV2 hashes)> CreateTorrent(string path) + { + var engine = new ClientEngine (EngineSettingsBuilder.CreateForTests (autoSaveLoadMagnetLinkMetadata: false)); + + var dict = (BEncodedDictionary) BEncodedDictionary.Decode (File.ReadAllBytes (V2OnlyTorrentPath)); + var rawLayers = (BEncodedDictionary) dict["piece layers"]; + dict.Remove ("piece layers"); + + var torrent = await Torrent.LoadAsync (dict.Encode ()); + var layers = new PieceHashesV2 (torrent.PieceLength, torrent.Files, rawLayers); + + var manager = await engine.AddAsync (torrent, "bbb"); + return (engine, manager, layers); + } + + [Test] + public async Task InitialStateIsCorrect () + { + (var engine, var manager, var layers) = await CreateTorrent (V2OnlyTorrentPath); + + var pieceHashesMode = new PieceHashesMode (manager, engine.DiskManager, engine.ConnectionManager, engine.Settings); + var peer = manager.AddConnectedPeer (supportsLTMetdata: true); + + Assert.AreEqual (manager.PieceHashes.Count, 0); + Assert.IsFalse (manager.PendingV2PieceHashes.AllFalse); + Assert.IsFalse (manager.PieceHashes.HasV2Hashes); + Assert.IsFalse (manager.PieceHashes.HasV1Hashes); + } + + [Test] + public async Task RequestHashes () + { + (var engine, var manager, var layers) = await CreateTorrent (V2OnlyTorrentPath); + var pieceHashesMode = new PieceHashesMode (manager, engine.DiskManager, engine.ConnectionManager, engine.Settings); + var peer = manager.AddConnectedPeer (supportsLTMetdata: true); + pieceHashesMode.HandleMessage (peer, new ExtendedHandshakeMessage (false, manager.Torrent.InfoMetadata.Length, 12345), default); + while (!manager.PendingV2PieceHashes.AllFalse && manager.PieceHashes.Count == 0 && !manager.PieceHashes.HasV2Hashes) { + pieceHashesMode.Tick (0); + PeerMessage message; + while ((message = peer.MessageQueue.TryDequeue ()) != null) + if (message is HashRequestMessage hashRequest) + pieceHashesMode.HandleMessage (peer, FulfillRequest (hashRequest, layers), default); + } + + Assert.AreEqual (manager.PieceHashes.Count, manager.Torrent.PieceCount); + Assert.IsTrue (manager.PendingV2PieceHashes.AllFalse); + Assert.IsTrue (manager.PieceHashes.HasV2Hashes); + Assert.IsFalse (manager.PieceHashes.HasV1Hashes); + } + + [Test] + public async Task RequestHashes_OnePeerDisconnects () + { + (var engine, var manager, var layers) = await CreateTorrent (V2OnlyTorrentPath); + + var pieceHashesMode = new PieceHashesMode (manager, engine.DiskManager, engine.ConnectionManager, engine.Settings); + var peer = manager.AddConnectedPeer (supportsLTMetdata: true); + pieceHashesMode.Tick (0); + Assert.AreNotEqual (0, peer.AmRequestingPiecesCount); + engine.ConnectionManager.CleanupSocket (manager, peer); + pieceHashesMode.HandlePeerDisconnected (peer); + Assert.AreEqual (0, manager.Peers.ConnectedPeers.Count); + + peer = manager.AddConnectedPeer (supportsLTMetdata: true); + + while (!manager.PendingV2PieceHashes.AllFalse && manager.PieceHashes.Count == 0 && !manager.PieceHashes.HasV2Hashes) { + pieceHashesMode.Tick (0); + PeerMessage message; + while ((message = peer.MessageQueue.TryDequeue ()) != null) + if (message is HashRequestMessage hashRequest) + pieceHashesMode.HandleMessage (peer, FulfillRequest (hashRequest, layers), default); + } + + Assert.AreEqual (manager.PieceHashes.Count, manager.Torrent.PieceCount); + Assert.IsTrue (manager.PendingV2PieceHashes.AllFalse); + Assert.IsTrue (manager.PieceHashes.HasV2Hashes); + Assert.IsFalse (manager.PieceHashes.HasV1Hashes); + } + + static HashesMessage FulfillRequest(HashRequestMessage hashRequest, PieceHashesV2 layers) + { + Memory totalBuffer = new byte[(hashRequest.Length + hashRequest.ProofLayers) * 32]; + var hashBuffer = totalBuffer.Slice (0, hashRequest.Length * 32); + var proofBuffer = totalBuffer.Slice (hashRequest.Length * 32, hashRequest.ProofLayers * 32); + Assert.IsTrue (layers.TryGetV2Hashes (hashRequest.PiecesRoot, hashRequest.BaseLayer, hashRequest.Index, hashRequest.Length, hashBuffer.Span, proofBuffer.Span, out int proofs)); + return new HashesMessage (hashRequest.PiecesRoot, hashRequest.BaseLayer, hashRequest.Index, hashRequest.Length, proofs, totalBuffer.Slice (0, (hashRequest.Length + proofs) * 32)); + } + } +} diff --git a/src/Tests/Tests.MonoTorrent.PiecePicking/PeerId.cs b/src/Tests/Tests.MonoTorrent.PiecePicking/PeerId.cs index f33e1df9a..98ffe0c51 100644 --- a/src/Tests/Tests.MonoTorrent.PiecePicking/PeerId.cs +++ b/src/Tests/Tests.MonoTorrent.PiecePicking/PeerId.cs @@ -3,7 +3,7 @@ namespace MonoTorrent.PiecePicking { - class PeerId : IPeerWithMessaging + class PeerId : IPeer { internal static PeerId CreateNull (int bitfieldLength) { @@ -31,7 +31,6 @@ internal static PeerId CreateNull (int bitfieldLength, bool seeder, bool isChoki public bool AmInterested { get; set; } public int AmRequestingPiecesCount { get; set; } - ReadOnlyBitField IPeer.BitField => BitField; public BitField BitField { get; private set; } public bool CanRequestMorePieces { get; set; } = true; public long DownloadSpeed { get; } diff --git a/src/Tests/Tests.MonoTorrent.PiecePicking/PiecePickerFilterChecker.cs b/src/Tests/Tests.MonoTorrent.PiecePicking/PiecePickerFilterChecker.cs index 8eec39e59..3072908fa 100644 --- a/src/Tests/Tests.MonoTorrent.PiecePicking/PiecePickerFilterChecker.cs +++ b/src/Tests/Tests.MonoTorrent.PiecePicking/PiecePickerFilterChecker.cs @@ -36,7 +36,7 @@ class PiecePickerFilterChecker : PiecePickerFilter { public List Initialised; public List<(IPeer peer, ReadOnlyBitField bitfield)> Interesting; - public List<(IPeer peer, ReadOnlyBitField available, IReadOnlyList otherPeers, int count, int startIndex, int endIndex)> Picks; + public List<(IPeer peer, ReadOnlyBitField available, IReadOnlyList otherPeers, int count, int startIndex, int endIndex)> Picks; public PiecePickerFilterChecker () : this (null) @@ -48,7 +48,7 @@ public PiecePickerFilterChecker (IPiecePicker next) { Initialised = new List (); Interesting = new List<(IPeer peer, ReadOnlyBitField bitfield)> (); - Picks = new List<(IPeer peer, ReadOnlyBitField available, IReadOnlyList otherPeers, int count, int startIndex, int endIndex)> (); + Picks = new List<(IPeer peer, ReadOnlyBitField available, IReadOnlyList otherPeers, int count, int startIndex, int endIndex)> (); } public override void Initialise (IPieceRequesterData torrentData) @@ -63,9 +63,9 @@ public override bool IsInteresting (IPeer peer, ReadOnlyBitField bitfield) return Next == null ? !bitfield.AllFalse : Next.IsInteresting (peer, bitfield); } - public override int PickPiece (IPeer peer, ReadOnlyBitField available, IReadOnlyList otherPeers, int startIndex, int endIndex, Span requests) + public override int PickPiece (IPeer peer, ReadOnlyBitField available, ReadOnlySpan otherPeers, int startIndex, int endIndex, Span requests) { - Picks.Add ((peer, new ReadOnlyBitField (available), new List (otherPeers).AsReadOnly (), requests.Length, startIndex, endIndex)); + Picks.Add ((peer, new ReadOnlyBitField (available), new List (otherPeers.ToArray ()).AsReadOnly (), requests.Length, startIndex, endIndex)); return Next == null ? 0 : Next.PickPiece (peer, available, otherPeers, startIndex, endIndex, requests); } } diff --git a/src/Tests/Tests.MonoTorrent.PiecePicking/PriorityPickerTests.cs b/src/Tests/Tests.MonoTorrent.PiecePicking/PriorityPickerTests.cs index 7d441d344..e37cfa3e6 100644 --- a/src/Tests/Tests.MonoTorrent.PiecePicking/PriorityPickerTests.cs +++ b/src/Tests/Tests.MonoTorrent.PiecePicking/PriorityPickerTests.cs @@ -50,7 +50,7 @@ public void Set (ITorrentManagerFile file, Priority priority) => ((TorrentFileInfo) file).Priority = priority; PiecePickerFilterChecker checker; - List peers; + ReadOnlyBitField[] peers; PriorityPicker picker; BitField singleBitfield; @@ -74,7 +74,7 @@ public void Setup () checker = new PiecePickerFilterChecker (); picker = new PriorityPicker (checker); - peers = new List (); + peers = new ReadOnlyBitField[0]; } static IPieceRequesterData CreateSingleFile () @@ -246,7 +246,7 @@ public void MultiFile_Highest_RestNormal () picker.Initialise (multiFile); Span buffer = stackalloc PieceSegment[1]; - picker.PickPiece (multiPeer, multiBitfield, new List (), 0, multiBitfield.Length - 1, buffer); + picker.PickPiece (multiPeer, multiBitfield, Array.Empty (), 0, multiBitfield.Length - 1, buffer); Assert.AreEqual (2, checker.Picks.Count, "#1"); Assert.IsTrue (picker.IsInteresting (multiPeer, multiBitfield), "#2"); Assert.IsTrue (new BitField (multiBitfield.Length).SetTrue (((TorrentFileInfo) multiFile.Files[1]).GetSelector ()).SequenceEqual (checker.Picks[0].available), "#3"); diff --git a/src/Tests/Tests.MonoTorrent.PiecePicking/RandomisedPickerTests.cs b/src/Tests/Tests.MonoTorrent.PiecePicking/RandomisedPickerTests.cs index 28a8fb6f3..9941d2e59 100644 --- a/src/Tests/Tests.MonoTorrent.PiecePicking/RandomisedPickerTests.cs +++ b/src/Tests/Tests.MonoTorrent.PiecePicking/RandomisedPickerTests.cs @@ -76,7 +76,7 @@ public void Pick () { // Pretend only the 1st piece is available. var onePiece = new BitField (seeder.BitField.Length).Set (0, true); - var piece = picker.PickPiece (seeder, onePiece, new List ()).Value; + var piece = picker.PickPiece (seeder, onePiece, Array.Empty ()).Value; // We should pick a random midpoint and select a piece starting from there. // If that fails we should wrap around to 0 and scan from the beginning. @@ -94,7 +94,7 @@ public void Pick () public void SinglePieceBitfield () { picker.Initialise (CreateOnePieceTorrentData ()); - picker.PickPiece (seeder, new BitField (1).SetAll (true), new List ()); + picker.PickPiece (seeder, new BitField (1).SetAll (true), Array.Empty ()); Assert.AreEqual (1, checker.Picks.Count, "#1"); Assert.AreEqual (0, checker.Picks[0].startIndex, "#2"); @@ -105,7 +105,7 @@ public void SinglePieceBitfield () public void SinglePieceRange () { Span buffer = stackalloc PieceSegment[1]; - picker.PickPiece (seeder, seeder.BitField, new List (), 12, 13, buffer); + picker.PickPiece (seeder, seeder.BitField, Array.Empty (), 12, 13, buffer); Assert.AreEqual (1, checker.Picks.Count, "#1"); Assert.AreEqual (12, checker.Picks[0].startIndex, "#2"); @@ -117,7 +117,7 @@ public void TwoPieceRange () { Span buffer = stackalloc PieceSegment[1]; var onePiece = new BitField (seeder.BitField.Length).Set (0, true); - picker.PickPiece (seeder, onePiece, new List (), 12, 14, buffer); + picker.PickPiece (seeder, onePiece, Array.Empty (), 12, 14, buffer); Assert.AreEqual (2, checker.Picks.Count, "#1"); Assert.AreEqual (13, checker.Picks[0].startIndex, "#2"); diff --git a/src/Tests/Tests.MonoTorrent.PiecePicking/RarestFirstPickerTests.cs b/src/Tests/Tests.MonoTorrent.PiecePicking/RarestFirstPickerTests.cs index e7cf9871b..38c050b82 100644 --- a/src/Tests/Tests.MonoTorrent.PiecePicking/RarestFirstPickerTests.cs +++ b/src/Tests/Tests.MonoTorrent.PiecePicking/RarestFirstPickerTests.cs @@ -43,7 +43,7 @@ public class RarestFirstPickerTests BitField bitfield; PiecePickerFilterChecker checker; PeerId peer; - List peers; + ReadOnlyBitField[] peers; RarestFirstPicker picker; IPieceRequesterData torrentData; @@ -68,18 +68,21 @@ public void Setup () peer = PeerId.CreateNull (pieces); peer.BitField.SetAll (true); - peers = new List (); + peers = new ReadOnlyBitField[5]; for (int i = 0; i < 5; i++) - peers.Add (PeerId.CreateNull (pieces)); + peers[i] = new BitField (pieces); } [Test] public void RarestPieceTest () { Span buffer = stackalloc PieceSegment[1]; - for (int i = 0; i < 5; i++) + for (int i = 0; i < 5; i++) { + var bf = new BitField (peers[i]); for (int j = 0; j < (i * 5) + 5; j++) - peers[i].BitField[j] = true; + bf[j] = true; + peers[i] = bf; + } // No pieces should be selected, but we can check what was requested. picker.PickPiece (peer, peer.BitField, peers, 0, peer.BitField.Length - 1, buffer); @@ -118,7 +121,7 @@ public void OnlyAvailablePiecesAllowed () // Every other peer has all pieces except for piece '2'. for (int i = 0; i < 5; i++) - peers[i].BitField.SetAll (true).Set (i, false); + peers[i] = new BitField (peers[i]).SetAll (true).Set (i, false); // Ensure that pieces which were not in the 'available' bitfield were not offered // as suggestions. diff --git a/src/Tests/Tests.MonoTorrent.PiecePicking/StandardPickerTests.cs b/src/Tests/Tests.MonoTorrent.PiecePicking/StandardPickerTests.cs index 26f0d6c78..5971ece46 100644 --- a/src/Tests/Tests.MonoTorrent.PiecePicking/StandardPickerTests.cs +++ b/src/Tests/Tests.MonoTorrent.PiecePicking/StandardPickerTests.cs @@ -42,7 +42,7 @@ public class StandardPickerTests { BitField bitfield; PeerId peer; - List peers; + ReadOnlyBitField[] peers; IPiecePicker picker; TestTorrentManagerInfo torrentData; @@ -57,16 +57,15 @@ public void Setup () pieceLength: pieceLength, size: pieceLength * pieceCount ); - peers = new List (); + peers = new ReadOnlyBitField[20]; picker = new StandardPicker (); picker.Initialise (torrentData); peer = PeerId.CreateNull (pieceCount); + peer.SupportsFastPeer = true; for (int i = 0; i < 20; i++) { - PeerId p = PeerId.CreateNull (pieceCount); - p.SupportsFastPeer = true; - peers.Add (p); + peers[i] = new BitField (pieceCount); } } @@ -74,24 +73,24 @@ public void Setup () public void RequestFastSeeder () { int[] allowedFast = { 1, 2, 3, 5, 8, 13, 21 }; - peers[0].SupportsFastPeer = true; - peers[0].IsAllowedFastPieces.AddRange ((int[]) allowedFast.Clone ()); + peer.SupportsFastPeer = true; + peer.IsAllowedFastPieces.AddRange ((int[]) allowedFast.Clone ()); - peers[0].BitField.SetAll (true); // Lets pretend he has everything + peer.BitField.SetAll (true); // Lets pretend he has everything for (int i = 0; i < 7; i++) { for (int j = 0; j < 16; j++) { - var msg = picker.PickPiece (peers[0], peers[0].BitField, peers); + var msg = picker.PickPiece (peer, peer.BitField, peers); Assert.IsNotNull (msg, "#1." + j); Assert.IsTrue (Array.IndexOf (allowedFast, msg.Value.PieceIndex) > -1, "#2." + j); } } - Assert.IsNull (picker.PickPiece (peers[0], peers[0].BitField, peers)); + Assert.IsNull (picker.PickPiece (peer, peer.BitField, peers)); } [Test] public void RequestEntireFastPiece () { - var id = peers[0]; + var id = peer; int[] allowedFast = { 1, 2 }; id.SupportsFastPeer = true; id.IsAllowedFastPieces.AddRange ((int[]) allowedFast.Clone ()); @@ -99,7 +98,7 @@ public void RequestEntireFastPiece () PieceSegment? request; var pieceRequests = new List (); - while ((request = picker.PickPiece (id, id.BitField, new List ())) != null) + while ((request = picker.PickPiece (id, id.BitField, Array.Empty ())) != null) pieceRequests.Add (request.Value); var expectedRequests = torrentData.TorrentInfo.BlocksPerPiece(1); @@ -113,44 +112,45 @@ public void RequestEntireFastPiece () [Test] public void RequestFastNotSeeder () { - peers[0].SupportsFastPeer = true; - peers[0].IsAllowedFastPieces.AddRange (new[] { 1, 2, 3, 5, 8, 13, 21 }); + peer.SupportsFastPeer = true; + peer.IsAllowedFastPieces.AddRange (new[] { 1, 2, 3, 5, 8, 13, 21 }); - peers[0].BitField.SetAll (true); - peers[0].BitField[1] = false; - peers[0].BitField[3] = false; - peers[0].BitField[5] = false; + peer.BitField.SetAll (true); + peer.BitField[1] = false; + peer.BitField[3] = false; + peer.BitField[5] = false; for (int i = 0; i < 4; i++) for (int j = 0; j < 16; j++) { - var m = picker.PickPiece (peers[0], peers[0].BitField, peers); + var m = picker.PickPiece (peer, peer.BitField, peers); Assert.IsTrue (m.Value.PieceIndex == 2 || m.Value.PieceIndex == 8 || m.Value.PieceIndex == 13 || m.Value.PieceIndex == 21); } - Assert.IsNull (picker.PickPiece (peers[0], peers[0].BitField, peers)); + Assert.IsNull (picker.PickPiece (peer, peer.BitField, peers)); } [Test] public void RequestChoked () { - Assert.IsNull (picker.PickPiece (peers[0], peers[0].BitField, peers)); + Assert.IsNull (picker.PickPiece (peer, peer.BitField, peers)); } [Test] public void StandardPicker_PickStandardPiece () { - peers[0].IsChoking = false; - peers[0].BitField.SetAll (true); + peer.IsChoking = false; + peer.BitField.SetAll (true); Span buffer = stackalloc PieceSegment[1]; bitfield[1] = true; - Assert.AreEqual (1, picker.PickPiece (peers[0], new BitField (bitfield).Not (), peers, 0, 10, buffer)); + Assert.AreEqual (1, picker.PickPiece (peer, new BitField (bitfield).Not (), peers, 0, 10, buffer)); Assert.AreEqual (0, buffer[0].PieceIndex); - peers[1].IsChoking = false; - peers[1].BitField.SetAll (true); - peers[1].RepeatedHashFails = peers[1].TotalHashFails = 1; - Assert.AreEqual (1, picker.PickPiece (peers[1], new BitField (bitfield).Not (), peers, 0, 10, buffer)); + var other = PeerId.CreateNull (peer.BitField.Length); + other.IsChoking = false; + other.BitField.SetAll (true); + other.RepeatedHashFails = other.TotalHashFails = 1; + Assert.AreEqual (1, picker.PickPiece (other, new BitField (bitfield).Not (), peers, 0, 10, buffer)); Assert.AreEqual (2, buffer[0].PieceIndex); } @@ -174,7 +174,7 @@ public void CancelRequests () picker.PickPiece (peer, peer.BitField, peers); Assert.AreEqual (torrentData.TotalBlocks, messages.Count, "#0"); - picker.CancelRequests (peer); + picker.CancelRequests (peer, 0, peer.BitField.Length - 1, stackalloc PieceSegment[peer.AmRequestingPiecesCount]); var messages2 = new HashSet (); while ((m = picker.PickPiece (peer, peer.BitField, peers)) != null) @@ -192,7 +192,7 @@ public void PeerChoked_ReceivedOneBlock () peer.IsChoking = false; peer.BitField.SetAll (true); - var otherPeer = peers[1]; + var otherPeer = PeerId.CreateNull (peer.BitField.Length); otherPeer.BitField.SetAll (true); PieceSegment? m; @@ -203,7 +203,7 @@ public void PeerChoked_ReceivedOneBlock () Assert.AreEqual (torrentData.TotalBlocks, messages.Count, "#0"); picker.ValidatePiece (peer, messages[0], out _, out _); messages.RemoveAt (0); - picker.CancelRequests (peer); + picker.CancelRequests (peer, 0, peer.BitField.Length - 1, stackalloc PieceSegment[peer.AmRequestingPiecesCount]); peer.IsChoking = true; otherPeer.IsChoking = true; @@ -225,7 +225,7 @@ public void RepeatedHashFails_CannotContinueExisting () peer.IsChoking = false; peer.BitField.SetAll (true); - var otherPeer = peers[1]; + var otherPeer = PeerId.CreateNull (peer.BitField.Length); otherPeer.IsChoking = false; otherPeer.BitField.SetAll (true); otherPeer.RepeatedHashFails = otherPeer.TotalHashFails = 1; @@ -234,7 +234,7 @@ public void RepeatedHashFails_CannotContinueExisting () var request = picker.PickPiece (peer, peer.BitField, peers); picker.ValidatePiece (peer, request.Value, out _, out _); request = picker.PickPiece (peer, peer.BitField, peers); - picker.CancelRequests (peer); + picker.CancelRequests (peer, 0, peer.BitField.Length - 1, stackalloc PieceSegment[peer.AmRequestingPiecesCount]); // Peers involved in repeated hash fails cannot continue incomplete pieces. var otherRequest = picker.PickPiece (otherPeer, otherPeer.BitField, peers); @@ -247,7 +247,7 @@ public void DoesNotHavePiece_CannotContinueExisting () peer.IsChoking = false; peer.BitField.SetAll (true); - var otherPeer = peers[1]; + var otherPeer = PeerId.CreateNull (peer.BitField.Length); otherPeer.IsChoking = false; otherPeer.BitField.SetAll (true); @@ -255,7 +255,7 @@ public void DoesNotHavePiece_CannotContinueExisting () var request = picker.PickPiece (peer, peer.BitField, peers); picker.ValidatePiece (peer, request.Value, out _, out _); request = picker.PickPiece (peer, peer.BitField, peers); - picker.CancelRequests (peer); + picker.CancelRequests (peer, 0, peer.BitField.Length - 1, stackalloc PieceSegment[peer.AmRequestingPiecesCount]); otherPeer.BitField[request.Value.PieceIndex] = false; // We cannot request a block if the peer doesn't have it. @@ -270,7 +270,7 @@ public void PeerDisconnected_ReceivedOneBlock () peer.IsChoking = false; peer.BitField.SetAll (true); - var otherPeer = peers[1]; + var otherPeer = PeerId.CreateNull (peer.BitField.Length); otherPeer.BitField.SetAll (true); PieceSegment? m; @@ -281,7 +281,7 @@ public void PeerDisconnected_ReceivedOneBlock () Assert.AreEqual (torrentData.TotalBlocks, messages.Count, "#0"); picker.ValidatePiece (peer, messages[0], out _, out _); messages.RemoveAt (0); - picker.CancelRequests (peer); + picker.CancelRequests (peer, 0, peer.BitField.Length - 1, stackalloc PieceSegment[peer.AmRequestingPiecesCount]); otherPeer.IsChoking = true; Assert.IsNull (picker.PickPiece (otherPeer, otherPeer.BitField, peers)); @@ -330,7 +330,7 @@ public void PeerChoked () while ((m = picker.PickPiece (peer, peer.BitField, peers)) != null) messages.Add (m.Value); - picker.CancelRequests (peer); + picker.CancelRequests (peer, 0, peer.BitField.Length - 1, stackalloc PieceSegment[peer.AmRequestingPiecesCount]); var messages2 = new HashSet (); while ((m = picker.PickPiece (peer, peer.BitField, peers)) != null) @@ -353,7 +353,7 @@ public void ChokeThenClose () while ((m = picker.PickPiece (peer, peer.BitField, peers)) != null) messages.Add (m.Value); - picker.CancelRequests (peer); + picker.CancelRequests (peer, 0, peer.BitField.Length - 1, stackalloc PieceSegment[peer.AmRequestingPiecesCount]); var messages2 = new HashSet (); while ((m = picker.PickPiece (peer, peer.BitField, peers)) != null) @@ -418,7 +418,7 @@ public void CompletePartialTest () peer.BitField.SetAll (true); var message = picker.PickPiece (peer, peer.BitField, peers); Assert.IsTrue (picker.ValidatePiece (peer, message.Value, out bool pieceComplete, out IList peersInvolved), "#1"); - picker.CancelRequests (peer); + picker.CancelRequests (peer, 0, peer.BitField.Length - 1, stackalloc PieceSegment[peer.AmRequestingPiecesCount]); for (int i = 1; i < torrentData.TorrentInfo.BlocksPerPiece (0); i++) { message = picker.PickPiece (peer, peer.BitField, peers); Assert.IsTrue (picker.ValidatePiece (peer, message.Value, out pieceComplete, out peersInvolved), "#2." + i); @@ -509,9 +509,10 @@ public void PickBundle_2 () public void PickBundle_3 () { var messages = new List (); - peers[2].IsChoking = false; - peers[2].BitField.SetAll (true); - messages.Add (picker.PickPiece (peers[2], peers[2].BitField, peers).Value); + var otherPeer = PeerId.CreateNull (peer.BitField.Length); + otherPeer.IsChoking = false; + otherPeer.BitField.SetAll (true); + messages.Add (picker.PickPiece (otherPeer, otherPeer.BitField, peers).Value); peer.IsChoking = false; @@ -526,7 +527,7 @@ public void PickBundle_3 () for (int i = 0; i < requested; i++) messages.Add (buffer[i]); } - while ((request = picker.ContinueAnyExistingRequest (peer, 0, bitfield.Length - 1)) != null) + while ((request = picker.ContinueAnyExistingRequest (peer, peer.BitField, 0, bitfield.Length - 1)) != null) messages.Add (request.Value); Assert.AreEqual (torrentData.TorrentInfo.BlocksPerPiece (0) * 7, messages.Count, "#2"); @@ -536,16 +537,16 @@ public void PickBundle_3 () public void PickBundle4 () { Span buffer = stackalloc PieceSegment[1]; - peers[0].IsChoking = false; - peers[0].BitField.SetAll (true); + peer.IsChoking = false; + peer.BitField.SetAll (true); for (int i = 0; i < torrentData.TorrentInfo.BlocksPerPiece (0); i++) - picker.PickPiece (peers[0], peers[0].BitField, new List (), 4, 4, buffer); + picker.PickPiece (peer, peer.BitField, Array.Empty (), 4, 4, buffer); for (int i = 0; i < torrentData.TorrentInfo.BlocksPerPiece (0); i++) - picker.PickPiece (peers[0], peers[0].BitField, new List (), 6, 6, buffer); + picker.PickPiece (peer, peer.BitField, Array.Empty (), 6, 6, buffer); buffer = stackalloc PieceSegment[20 * torrentData.TorrentInfo.BlocksPerPiece (0)]; - var b = picker.PickPiece (peers[0], peers[0].BitField, new List (), buffer); + var b = picker.PickPiece (peer, peer.BitField, Array.Empty (), buffer); for (int i = 0; i < b; i++) Assert.IsTrue (buffer[i].PieceIndex > 6); } @@ -557,10 +558,10 @@ public void Pick20SequentialPieces () foreach (var i in Enumerable.Range (0, 5).Concat (Enumerable.Range (10, 20))) bitfield[i] = true; - peers[0].IsChoking = false; + peer.IsChoking = false; Span buffer = stackalloc PieceSegment[20 * torrentData.TorrentInfo.BlocksPerPiece (0)]; - var b = picker.PickPiece (peers[0], bitfield, new List (), buffer); + var b = picker.PickPiece (peer, bitfield, Array.Empty (), buffer); Assert.AreEqual (20 * torrentData.TorrentInfo.BlocksPerPiece (0), b); for (int i = 0; i < b; i++) Assert.IsTrue (buffer[i].PieceIndex >= 10 && buffer[i].PieceIndex < 30); @@ -571,21 +572,21 @@ public void PickBundle6 () { bitfield.SetAll (false); - peers[0].IsChoking = false; - peers[0].BitField.SetAll (true); + peer.IsChoking = false; + peer.BitField.SetAll (true); Span buffer = stackalloc PieceSegment[1]; for (int i = 0; i < torrentData.TorrentInfo.BlocksPerPiece (0); i++) - picker.PickPiece (peers[0], peers[0].BitField, new List (), 0, 0, buffer); + picker.PickPiece (peer, peer.BitField, Array.Empty (), 0, 0, buffer); for (int i = 0; i < torrentData.TorrentInfo.BlocksPerPiece (0); i++) - picker.PickPiece (peers[0], peers[0].BitField, new List (), 1, 1, buffer); + picker.PickPiece (peer, peer.BitField, Array.Empty (), 1, 1, buffer); for (int i = 0; i < torrentData.TorrentInfo.BlocksPerPiece (0); i++) - picker.PickPiece (peers[0], peers[0].BitField, new List (), 3, 3, buffer); + picker.PickPiece (peer, peer.BitField, Array.Empty (), 3, 3, buffer); for (int i = 0; i < torrentData.TorrentInfo.BlocksPerPiece (0); i++) - picker.PickPiece (peers[0], peers[0].BitField, new List (), 6, 6, buffer); + picker.PickPiece (peer, peer.BitField, Array.Empty (), 6, 6, buffer); buffer = stackalloc PieceSegment[2 * torrentData.TorrentInfo.BlocksPerPiece (0)]; - var b = picker.PickPiece (peers[0], peers[0].BitField, new List (), buffer); + var b = picker.PickPiece (peer, peer.BitField, Array.Empty (), buffer); Assert.AreEqual (2 * torrentData.TorrentInfo.BlocksPerPiece (0), b); for (int i = 0; i < b; i++) Assert.IsTrue (buffer[i].PieceIndex >= 4 && buffer[i].PieceIndex < 6); @@ -594,14 +595,16 @@ public void PickBundle6 () [Test] public void FastPieceTest () { + var fastPeers = new PeerId[2]; for (int i = 0; i < 2; i++) { - peers[i].BitField.SetAll (true); - peers[i].SupportsFastPeer = true; - peers[i].IsAllowedFastPieces.Add (5); - peers[i].IsAllowedFastPieces.Add (6); + fastPeers[i] = PeerId.CreateNull (peer.BitField.Length); + fastPeers[i].BitField.SetAll (true); + fastPeers[i].SupportsFastPeer = true; + fastPeers[i].IsAllowedFastPieces.Add (5); + fastPeers[i].IsAllowedFastPieces.Add (6); } - var m1 = picker.PickPiece (peers[0], peers[0].BitField).Value; - var m2 = picker.PickPiece (peers[1], peers[1].BitField).Value; + var m1 = picker.PickPiece (fastPeers[0], fastPeers[0].BitField).Value; + var m2 = picker.PickPiece (fastPeers[1], fastPeers[1].BitField).Value; Assert.AreNotEqual (m1.PieceIndex, m2.PieceIndex, "#1"); } @@ -610,9 +613,9 @@ public void DupeRequests_PickSameBlockTwiceWhenAllRequested () { var seeder1 = PeerId.CreateNull (bitfield.Length, true, false, true); var seeder2 = PeerId.CreateNull (bitfield.Length, true, false, true); - var singlePiece = peers[0].BitField.SetAll (false).Set (3, true); + var singlePiece = peer.BitField.SetAll (false).Set (3, true); - Assert.IsNull (picker.ContinueAnyExistingRequest (seeder1, 0, bitfield.Length)); + Assert.IsNull (picker.ContinueAnyExistingRequest (seeder1, seeder1.BitField, 0, bitfield.Length)); PieceSegment? req; var requests1 = new List (); @@ -623,13 +626,13 @@ public void DupeRequests_PickSameBlockTwiceWhenAllRequested () Assert.IsNull (picker.ContinueExistingRequest (seeder2, 0, bitfield.Length)); // Every block has been requested once and no duplicates are allowed. - Assert.IsNull (picker.ContinueAnyExistingRequest (seeder2, 0, bitfield.Length)); + Assert.IsNull (picker.ContinueAnyExistingRequest (seeder2, seeder2.BitField, 0, bitfield.Length)); // Every block has been requested once and no duplicates are allowed. - Assert.IsNull (picker.ContinueAnyExistingRequest (seeder2, 0, bitfield.Length, 1)); + Assert.IsNull (picker.ContinueAnyExistingRequest (seeder2, seeder2.BitField, 0, bitfield.Length, 1)); var requests2 = new List (); - while ((req = picker.ContinueAnyExistingRequest (seeder2, 0, bitfield.Length, 2)) != null) + while ((req = picker.ContinueAnyExistingRequest (seeder2, seeder2.BitField, 0, bitfield.Length, 2)) != null) requests2.Add (req.Value); CollectionAssert.AreEquivalent (requests1, requests2); @@ -640,9 +643,9 @@ public void DupeRequests_ValidateDupeThenPrimary () { var seeder1 = PeerId.CreateNull (bitfield.Length, true, false, true); var seeder2 = PeerId.CreateNull (bitfield.Length, true, false, true); - var singlePiece = peers[0].BitField.SetAll (false).Set (3, true); + var singlePiece = peer.BitField.SetAll (false).Set (3, true); - Assert.IsNull (picker.ContinueAnyExistingRequest (seeder1, 0, bitfield.Length)); + Assert.IsNull (picker.ContinueAnyExistingRequest (seeder1, seeder1.BitField, 0, bitfield.Length)); PieceSegment? req; var requests1 = new List (); @@ -650,7 +653,7 @@ public void DupeRequests_ValidateDupeThenPrimary () requests1.Add (req.Value); // This piece has been requested by both peers now. - PieceSegment request = picker.ContinueAnyExistingRequest (seeder2, 0, bitfield.Length, 2).Value; + PieceSegment request = picker.ContinueAnyExistingRequest (seeder2, seeder2.BitField, 0, bitfield.Length, 2).Value; // Validate the duplicate request first. Assert.IsTrue (picker.ValidatePiece (seeder2, request, out _, out _)); @@ -663,9 +666,9 @@ public void DupeRequests_ValidatePrimaryThenDupe () { var seeder1 = PeerId.CreateNull (bitfield.Length, true, false, true); var seeder2 = PeerId.CreateNull (bitfield.Length, true, false, true); - var singlePiece = peers[0].BitField.SetAll (false).Set (3, true); + var singlePiece = peer.BitField.SetAll (false).Set (3, true); - Assert.IsNull (picker.ContinueAnyExistingRequest (seeder1, 0, bitfield.Length)); + Assert.IsNull (picker.ContinueAnyExistingRequest (seeder1, seeder1.BitField, 0, bitfield.Length)); PieceSegment? req; var requests1 = new List (); @@ -673,7 +676,7 @@ public void DupeRequests_ValidatePrimaryThenDupe () requests1.Add (req.Value); // This piece has been requested by both peers now. - PieceSegment request = picker.ContinueAnyExistingRequest (seeder2, 0, bitfield.Length, 2).Value; + PieceSegment request = picker.ContinueAnyExistingRequest (seeder2, seeder2.BitField, 0, bitfield.Length, 2).Value; // Validate the primary request first Assert.IsTrue (picker.ValidatePiece (seeder1, request, out _, out _)); @@ -686,9 +689,9 @@ public void DupeRequests_FinalBlock_ValidatePrimaryThenDupe () { var seeder1 = PeerId.CreateNull (bitfield.Length, true, false, true); var seeder2 = PeerId.CreateNull (bitfield.Length, true, false, true); - var singlePiece = peers[0].BitField.SetAll (false).Set (3, true); + var singlePiece = peer.BitField.SetAll (false).Set (3, true); - Assert.IsNull (picker.ContinueAnyExistingRequest (seeder1, 0, bitfield.Length)); + Assert.IsNull (picker.ContinueAnyExistingRequest (seeder1, seeder1.BitField, 0, bitfield.Length)); PieceSegment? req; var requests = new List (); @@ -699,7 +702,7 @@ public void DupeRequests_FinalBlock_ValidatePrimaryThenDupe () Assert.IsTrue (picker.ValidatePiece (seeder1, requests[i], out _, out _)); // This should be the final unrequested block - PieceSegment request = picker.ContinueAnyExistingRequest (seeder2, 0, bitfield.Length, 2).Value; + PieceSegment request = picker.ContinueAnyExistingRequest (seeder2, seeder2.BitField, 0, bitfield.Length, 2).Value; Assert.AreEqual (requests[2], request); // Validate the primary request first @@ -720,9 +723,9 @@ public void DupeRequests_FinalBlock_ValidateDupleThenPrimary () { var seeder1 = PeerId.CreateNull (bitfield.Length, true, false, true); var seeder2 = PeerId.CreateNull (bitfield.Length, true, false, true); - var singlePiece = peers[0].BitField.SetAll (false).Set (3, true); + var singlePiece = peer.BitField.SetAll (false).Set (3, true); - Assert.IsNull (picker.ContinueAnyExistingRequest (seeder1, 0, bitfield.Length)); + Assert.IsNull (picker.ContinueAnyExistingRequest (seeder1, seeder1.BitField, 0, bitfield.Length)); PieceSegment? req; var requests = new List (); @@ -733,7 +736,7 @@ public void DupeRequests_FinalBlock_ValidateDupleThenPrimary () Assert.IsTrue (picker.ValidatePiece (seeder1, requests[i], out _, out _)); // This should be the final unrequested block - PieceSegment request = picker.ContinueAnyExistingRequest (seeder2, 0, bitfield.Length, 2).Value; + PieceSegment request = picker.ContinueAnyExistingRequest (seeder2, seeder2.BitField, 0, bitfield.Length, 2).Value; Assert.AreEqual (requests[2], request); // Validate the dupe request first @@ -755,27 +758,27 @@ public void DupeRequests_PeerCannotDuplicateOwnRequest () var seeder = PeerId.CreateNull (bitfield.Length, true, false, true); var singlePiece = new BitField (seeder.BitField).SetAll (false).Set (3, true); - Assert.IsNull (picker.ContinueAnyExistingRequest (seeder, 0, bitfield.Length - 1)); + Assert.IsNull (picker.ContinueAnyExistingRequest (seeder, seeder.BitField, 0, bitfield.Length - 1)); PieceSegment? req; var requests = new List (); while ((req = picker.PickPiece (seeder, singlePiece)) != null) requests.Add (req.Value); - Assert.IsNull (picker.ContinueAnyExistingRequest (seeder, 0, bitfield.Length - 1)); - Assert.IsNull (picker.ContinueAnyExistingRequest (seeder, 0, bitfield.Length - 1, 2)); + Assert.IsNull (picker.ContinueAnyExistingRequest (seeder, seeder.BitField, 0, bitfield.Length - 1)); + Assert.IsNull (picker.ContinueAnyExistingRequest (seeder, seeder.BitField, 0, bitfield.Length - 1, 2)); } [Test] public void DupeRequests_CanRequestInTriplicate () { - var seeders = new IPeer[] { + var seeders = new PeerId[] { PeerId.CreateNull (bitfield.Length, true, false, true), PeerId.CreateNull (bitfield.Length, true, false, true), PeerId.CreateNull (bitfield.Length, true, false, true), }; - var queue = new Queue (seeders); + var queue = new Queue (seeders); var requests = seeders.ToDictionary (t => t, t => new List ()); var singlePiece = new BitField (seeders[0].BitField).SetAll (false).Set (3, true); @@ -783,7 +786,7 @@ public void DupeRequests_CanRequestInTriplicate () // issuing duplicates. In the end all peers should have the same set though. while (true) { var req = picker.PickPiece (seeders[0], singlePiece) - ?? picker.ContinueAnyExistingRequest (seeders[0], 0, bitfield.Length - 1, 3); + ?? picker.ContinueAnyExistingRequest (seeders[0], seeders[0].BitField, 0, bitfield.Length - 1, 3); if (req.HasValue) requests[seeders[0]].Add (req.Value); else @@ -794,7 +797,7 @@ public void DupeRequests_CanRequestInTriplicate () while (queue.Count > 0) { var seeder = queue.Dequeue (); var req = picker.PickPiece (seeder, singlePiece) - ?? picker.ContinueAnyExistingRequest (seeder, 0, bitfield.Length - 1, 3); + ?? picker.ContinueAnyExistingRequest (seeder, seeder.BitField, 0, bitfield.Length - 1, 3); if (req.HasValue) { queue.Enqueue (seeder); diff --git a/src/Tests/Tests.MonoTorrent.PiecePicking/StreamingPieceRequesterTests.cs b/src/Tests/Tests.MonoTorrent.PiecePicking/StreamingPieceRequesterTests.cs index 12d834763..df6e7788f 100644 --- a/src/Tests/Tests.MonoTorrent.PiecePicking/StreamingPieceRequesterTests.cs +++ b/src/Tests/Tests.MonoTorrent.PiecePicking/StreamingPieceRequesterTests.cs @@ -40,7 +40,7 @@ namespace MonoTorrent.PiecePicking [TestFixture] public class StreamingPieceRequesterTests { - IPieceRequesterData CreateTorrentInfo () + TestTorrentManagerInfo CreateTorrentInfo () { var files = TorrentFileInfo.Create (Constants.BlockSize * 8, 1024 * 1024 * 8); return TestTorrentManagerInfo.Create ( @@ -59,14 +59,14 @@ public void PickFromBeforeHighPrioritySet () .Set (0, false); var requester = new StreamingPieceRequester (); - requester.Initialise (data, new[] { ignoringBitfield }); + requester.Initialise (data, data, new[] { ignoringBitfield }); requester.SeekToPosition (data.Files[0], data.PieceLength * 3); var peer = PeerId.CreateNull (ignoringBitfield.Length, true, false, true); - requester.AddRequests (peer, Array.Empty ()); + requester.AddRequests (peer, peer.BitField, Array.Empty ()); Assert.AreEqual (2, peer.AmRequestingPiecesCount); - var requests = peer.Requests; + var requests = data.Requests[peer]; Assert.AreEqual (2, requests.Count); Assert.IsTrue (requests.All (r => r.PieceIndex == 0)); } @@ -79,14 +79,14 @@ public void PickHighestPriority () .SetAll (false); var requester = new StreamingPieceRequester (); - requester.Initialise (data, new[] { ignoringBitfield }); + requester.Initialise (data, data, new[] { ignoringBitfield }); requester.SeekToPosition (data.Files[0], data.PieceLength * 3); var peer = PeerId.CreateNull (ignoringBitfield.Length, true, false, true); - requester.AddRequests (peer, Array.Empty ()); + requester.AddRequests (peer, peer.BitField, Array.Empty ()); Assert.AreEqual (4, peer.AmRequestingPiecesCount); - var requests = peer.Requests; + var requests = data.Requests[peer]; Assert.AreEqual (4, requests.Count); Assert.IsTrue (requests.All (r => r.PieceIndex == 3)); } diff --git a/src/Tests/TorrentInfoHelpers.cs b/src/Tests/TorrentInfoHelpers.cs index c5294e29f..7a7d4c6f1 100644 --- a/src/Tests/TorrentInfoHelpers.cs +++ b/src/Tests/TorrentInfoHelpers.cs @@ -35,7 +35,7 @@ namespace MonoTorrent { - class TestTorrentManagerInfo : ITorrentManagerInfo, IPieceRequesterData + class TestTorrentManagerInfo : ITorrentManagerInfo, IPieceRequesterData, IMessageEnqueuer { public IList Files => TorrentInfo.Files.Cast ().ToList (); @@ -49,9 +49,12 @@ class TestTorrentManagerInfo : ITorrentManagerInfo, IPieceRequesterData public int TotalBlocks => (int) Math.Ceiling ((float) TorrentInfo.Size / Constants.BlockSize); - int IPieceRequesterData.PieceCount => TorrentInfo.PieceCount (); + public int PieceCount => TorrentInfo.PieceCount (); - int IPieceRequesterData.PieceLength => TorrentInfo.PieceLength; + public int PieceLength => TorrentInfo.PieceLength; + + public Dictionary> Cancellations = new Dictionary> (); + public Dictionary> Requests = new Dictionary> (); public static TestTorrentManagerInfo Create ( int? pieceLength = null, @@ -82,15 +85,34 @@ public static T Create ( }; } - int IPieceRequesterData.BlocksPerPiece (int piece) + int IPieceRequesterData.SegmentsPerPiece (int piece) => TorrentInfo.BlocksPerPiece (piece); int IPieceRequesterData.ByteOffsetToPieceIndex (long byteOffset) => TorrentInfo.ByteOffsetToPieceIndex (byteOffset); - int IPieceRequesterData.BytesPerPiece (int piece) => TorrentInfo.BytesPerPiece (piece); + + void IMessageEnqueuer.EnqueueRequest (IPeer peer, PieceSegment block) + => ((IMessageEnqueuer) this).EnqueueRequests (peer, stackalloc PieceSegment[] { block }); + + void IMessageEnqueuer.EnqueueRequests (IPeer peer, Span blocks) + { + if (!Requests.TryGetValue (peer, out List requests)) + Requests[peer] = requests = new List (); + requests.AddRange (blocks.ToArray ()); + } + + void IMessageEnqueuer.EnqueueCancellation (IPeer peer, PieceSegment segment) + => ((IMessageEnqueuer) this).EnqueueCancellations (peer, stackalloc PieceSegment[] { segment }); + + void IMessageEnqueuer.EnqueueCancellations (IPeer peer, Span segments) + { + if (!Cancellations.TryGetValue (peer, out List cancellations)) + Cancellations[peer] = cancellations = new List (); + cancellations.AddRange (segments.ToArray ()); + } } class TestTorrentInfo : ITorrentInfo