/// <summary> /// Request piece blocks from passed in peer list. /// </summary> /// <param name="tc"></param> /// <param name="pieceNumber"></param> /// <param name="remotePeers"></param> /// <param name="stopwatch"></param> private bool GetMoreBlocks(TorrentContext tc, UInt32 pieceNumber, Peer[] remotePeers) { bool success = true; tc.assemblyData.guardMutex.WaitOne(); try { UInt32 blockOffset = 0; int currentPeer = 0; tc.assemblyData.currentBlockRequests = 0; foreach (var blockThere in tc.assemblyData.pieceBuffer.BlocksPresent) { if (!blockThere) { PWP.Request(remotePeers[currentPeer], pieceNumber, blockOffset, Math.Min(Constants.BlockSize, tc.GetPieceLength(pieceNumber) - blockOffset)); remotePeers[currentPeer].OutstandingRequestsCount++; if (++tc.assemblyData.currentBlockRequests == _maximumBlockRequests) { break; } currentPeer = (currentPeer + 1) % (int)remotePeers.Length; } blockOffset += Constants.BlockSize; } } catch (Exception ex) { Log.Logger.Error(ex); success = false; } tc.assemblyData.guardMutex.ReleaseMutex(); return(success); }
internal BlockingCollection <PieceRequest> pieceRequestQueue; // Piece request read queue /// <summary> /// Read/Write piece buffers to/from torrent on disk. /// </summary> /// <param name="tc"></param> /// <param name="transferBuffer"></param> /// <param name="read"></param> /// <summary> private void TransferPiece(TorrentContext tc, PieceBuffer transferBuffer, bool read) { int bytesTransferred = 0; UInt64 startOffset = transferBuffer.Number * tc.pieceLength; UInt64 endOffset = startOffset + transferBuffer.Length; foreach (var file in tc.filesToDownload) { if ((startOffset <= (file.offset + file.length)) && (file.offset <= endOffset)) { UInt64 startTransfer = Math.Max(startOffset, file.offset); UInt64 endTransfer = Math.Min(endOffset, file.offset + file.length); using (Stream stream = new FileStream(file.name, FileMode.Open)) { stream.Seek((Int64)(startTransfer - file.offset), SeekOrigin.Begin); if (read) { stream.Read(transferBuffer.Buffer, (Int32)(startTransfer % tc.pieceLength), (Int32)(endTransfer - startTransfer)); } else { stream.Write(transferBuffer.Buffer, (Int32)(startTransfer % tc.pieceLength), (Int32)(endTransfer - startTransfer)); } } bytesTransferred += (Int32)(endTransfer - startTransfer); if (bytesTransferred == tc.GetPieceLength(transferBuffer.Number)) { break; } } } }
/// <summary> /// Creates the torrent bitfield and piece information structures from the current disc /// which details the state of the torrent. ie. what needs to be downloaded still. /// </summary> /// <param name="tc"></param> internal void CreateTorrentBitfield(TorrentContext tc) { if (tc is null) { throw new ArgumentNullException(nameof(tc)); } byte[] pieceBuffer = new byte[tc.pieceLength]; UInt32 pieceNumber = 0; UInt32 bytesInBuffer = 0; int bytesRead = 0; Log.Logger.Debug("Generate pieces downloaded map from local files ..."); foreach (var file in tc.filesToDownload) { Log.Logger.Debug($"File: {file.name}"); using (var inFileSteam = new FileStream(file.name, FileMode.Open)) { while ((bytesRead = inFileSteam.Read(pieceBuffer, (Int32)bytesInBuffer, pieceBuffer.Length - (Int32)bytesInBuffer)) > 0) { bytesInBuffer += (UInt32)bytesRead; if (bytesInBuffer == tc.pieceLength) { UpdateBitfieldFromBuffer(tc, pieceNumber, pieceBuffer, bytesInBuffer); bytesInBuffer = 0; pieceNumber++; } } } } if (bytesInBuffer > 0) { UpdateBitfieldFromBuffer(tc, pieceNumber, pieceBuffer, bytesInBuffer); } Log.Logger.Debug("Finished generating downloaded map."); }
/// <summary> /// Request a piece block by block and wait for it to be assembled. If a timeout happens /// during assembly close the offending peers and start again with a different piece. /// </summary> /// <param name="tc"></param> /// <param name="pieceNumber"></param> /// <param name="waitHandles"></param> /// <returns></returns> private bool GetPieceFromPeers(TorrentContext tc, UInt32 pieceNumber, WaitHandle[] waitHandles) { Peer[] remotePeers = tc.selector.GetListOfPeers(tc, pieceNumber, _maximumBlockRequests); if (remotePeers.Length != 0) { var pieceAssemblyTimer = new Stopwatch(); Log.Logger.Debug($"Piece {pieceNumber} being assembled by {remotePeers.Length} peers."); tc.assemblyData.pieceBuffer = new PieceBuffer(tc, tc.GetPieceLength(pieceNumber)) { Number = pieceNumber }; tc.assemblyData.blockRequestsDone.Reset(); tc.MarkPieceMissing(pieceNumber, false); while (GetMoreBlocks(tc, pieceNumber, remotePeers)) { // // Wait for blocks to arrive // pieceAssemblyTimer.Start(); switch (WaitHandle.WaitAny(waitHandles, _assemberTimeout * 1000)) { // Any outstanding requests have been completed case 0: tc.assemblyData.blockRequestsDone.Reset(); if (tc.assemblyData.pieceBuffer.AllBlocksThere) { pieceAssemblyTimer.Stop(); tc.assemblyData.averageAssemblyTime.Add(pieceAssemblyTimer.ElapsedMilliseconds); Log.Logger.Info($"Download speed {tc.BytesPerSecond()} bytes/sec"); return(true); } break; // Assembly has been cancelled by external source case 1: return(false); // Timeout so bailout and try again // Note: Remove any peers that are not choked and had outstanding requests case WaitHandle.WaitTimeout: Log.Logger.Debug($"Timeout assembling piece {pieceNumber}."); tc.assemblyData.totalTimeouts++; foreach (var peer in remotePeers) { if ((peer.OutstandingRequestsCount > 0) && peer.PeerChoking.WaitOne(0)) { Log.Logger.Debug($"Closed peer {peer.Ip} with outstanding requests."); peer.Close(); } peer.OutstandingRequestsCount = 0; } return(false); } } return(false); } Thread.Sleep(1000); Log.Logger.Debug($"Zero peers to assemble piece {pieceNumber}."); return(false); }
/// <summary> /// Piece assembler task. If/once downboad is complete then start seeding the torrent until /// a cancel request is sent. Note: For seeding we only send announce requests every 30 minutes /// to stop the local and remote peers from being swamped by requests. /// </summary> /// <param name="tc"></param> /// <param name="cancelAssemblerTask"></param> internal void AssemblePieces(TorrentContext tc) { if (tc is null) { throw new ArgumentNullException(nameof(tc)); } Log.Logger.Debug($"Starting block assembler for InfoHash {Util.InfoHashToString(tc.infoHash)}."); try { tc.paused.WaitOne(tc.assemblyData.cancelTaskSource.Token); if (tc.MainTracker.Left != 0) { Log.Logger.Info("Torrent downloading..."); tc.Status = TorrentStatus.Downloading; AssembleMissingPieces(tc, tc.assemblyData.cancelTaskSource.Token); tc.MainTracker.ChangeStatus(TrackerEvent.completed); Log.Logger.Info("Whole Torrent finished downloading."); } Log.Logger.Info("Torrent seeding..."); tc.Status = TorrentStatus.Seeding; tc.MainTracker.SetSeedingInterval(60000 * 30); // Make sure get at least one more annouce before long wait tc.MainTracker.ChangeStatus(TrackerEvent.None); ProcessRemotePeerRequests(tc, tc.assemblyData.cancelTaskSource.Token); } catch (Exception ex) { Log.Logger.Error(ex); } Log.Logger.Debug($"Terminating block assembler for InfoHash {Util.InfoHashToString(tc.infoHash)}."); }
/// <summary> /// Setup data and resources needed by peer. /// </summary> /// <param name="ip">Ip.</param> /// <param name="port">Port.</param> /// <param name="infoHash">Info hash.</param> /// <param name="tc">torrent context.</param> /// public Peer(string ip, int port, TorrentContext tc, Socket socket) : this(ip, port, socket) { if (tc is null) { throw new ArgumentNullException(nameof(tc)); } SetTorrentContext(tc); }
/// <summary> /// Remove torrent context for an infohash. /// </summary> /// <param name="tc"></param> /// <returns></returns> internal bool RemoveTorrentContext(TorrentContext tc) { if (tc is null) { throw new ArgumentNullException(nameof(tc)); } return(_torrents.TryRemove(Util.InfoHashToString(tc.infoHash), out TorrentContext _)); }
/// <summary> /// Add torrent context for infohash. /// </summary> /// <param name="tc"></param> internal bool AddTorrentContext(TorrentContext tc) { if (tc is null) { throw new ArgumentNullException(nameof(tc)); } return(_torrents.TryAdd(Util.InfoHashToString(tc.infoHash), tc)); }
/// <summary> /// Initialise BitTorrent Tracker. /// </summary> /// <param name="tc"></param> public Tracker(TorrentContext tc) { trackerStatus = TrackerStatus.Stopped; PeerID = BitTorrentLibrary.PeerID.Get(); Ip = Host.GetIP(); _tc = tc ?? throw new ArgumentNullException(nameof(tc)); _tc.MainTracker = this; InfoHash = tc.infoHash; TrackerURL = tc.trackerURL; _announcer = new AnnouncerFactory().Create(TrackerURL); }
/// <summary> /// Retrieve torrent context for infohash. /// </summary> /// <param name="infohash"></param> /// <returns></returns> internal bool GetTorrentContext(byte[] infohash, out TorrentContext tc) { if (infohash is null) { throw new ArgumentNullException(nameof(infohash)); } if (_torrents.TryGetValue(Util.InfoHashToString(infohash), out tc)) { return(true); } return(false); }
public bool[] BlocksPresent => _blockPresent; // == true then block is present /// <summary> /// Create an empty piece buffer. /// </summary> /// <param name="length">Length.</param> public PieceBuffer(TorrentContext tc, UInt32 length) { Tc = tc ?? throw new ArgumentNullException(nameof(tc)); Number = 0; Length = length; Buffer = new byte[Length]; _blockCount = (int)Length / Constants.BlockSize; if (Length % Constants.BlockSize != 0) { _blockCount++; } _blockPresent = new bool[_blockCount]; }
/// <summary> /// Select the next piece to be downloaded. It does this by randomly generating a start /// piece and sequentially moving a long until ot finds a missing piece. /// </summary> /// <param name="tc"></param> /// <param name="nextPiece"></param> /// <param name="startPiece"></param> /// <param name="_"></param> /// <returns></returns> internal bool NextPiece(TorrentContext tc, ref UInt32 nextPiece) { if (tc is null) { throw new ArgumentNullException(nameof(tc)); } UInt32 startPiece = (UInt32)_pieceRandmizer.Next(0, tc.numberOfPieces - 1); (var pieceSuggested, var pieceNumber) = tc.FindNextMissingPiece(startPiece); nextPiece = pieceNumber; return(pieceSuggested); }
/// <summary> /// Update torrent piece information and bitfield from buffer. /// </summary> /// <param name="tc"></param> /// <param name="pieceNumber">Piece number.</param> /// <param name="pieceBuffer">Piece buffer.</param> /// <param name="numberOfBytes">Number of bytes in piece.</param> /// <summary> private void UpdateBitfieldFromBuffer(TorrentContext tc, UInt32 pieceNumber, byte[] pieceBuffer, UInt32 numberOfBytes) { bool pieceThere = tc.CheckPieceHash(pieceNumber, pieceBuffer, numberOfBytes); if (pieceThere) { tc.TotalBytesDownloaded += numberOfBytes; } tc.SetPieceLength(pieceNumber, numberOfBytes); tc.MarkPieceLocal(pieceNumber, pieceThere); if (!pieceThere) { tc.MarkPieceMissing(pieceNumber, true); } }
private readonly int _maximumBlockRequests; // Maximum requests at a time /// <summary> /// Signal to all peers in swarm that we now have the piece local so /// that they can request it if they need. /// </summary> /// <param name="tc"></param> /// <param name="pieceNumber"></param> private void SignalHaveToSwarm(TorrentContext tc, UInt32 pieceNumber) { foreach (var remotePeer in tc.peerSwarm.Values) { try { PWP.Have(remotePeer, pieceNumber); } catch (Exception ex) { Log.Logger.Error(ex); remotePeer.Close(); } } }
/// <summary> // Wait and process remote peer requests until cancelled. /// </summary> /// <param name="tc"></param> /// <param name="cancelTask"></param> private void ProcessRemotePeerRequests(TorrentContext tc, CancellationToken cancelAssemblerTask) { WaitHandle[] waitHandles = new WaitHandle[] { cancelAssemblerTask.WaitHandle }; foreach (var remotePeer in tc.peerSwarm.Values) { try { PWP.Uninterested(remotePeer); PWP.Unchoke(remotePeer); } catch (Exception ex) { Log.Logger.Error(ex); remotePeer.Close(); } } WaitHandle.WaitAll(waitHandles); }
/// <summary> /// Return list of peers connected that are not choked and have the piece. They /// are sorted in ascending order of average reponse time for a peer request packet /// reponse and limited by the value of maxPeers. /// </summary> /// <param name="tc"></param> /// <param name="pieceNumber"></param> /// <param name="maxPeers"></param> /// <returns></returns> internal Peer[] GetListOfPeers(TorrentContext tc, UInt32 pieceNumber, int maxPeers) { if (tc is null) { throw new ArgumentNullException(nameof(tc)); } List <Peer> peers = new List <Peer>(); foreach (var peer in tc.peerSwarm.Values) { if (peer.Connected && peer.PeerChoking.WaitOne(0) && peer.IsPieceOnRemotePeer(pieceNumber)) { peers.Add(peer); } } return(peers.OrderBy(peer => peer.AveragePacketResponse.Get()).ToList().Take(maxPeers).ToArray()); }
/// <summary> /// Creates the empty files on disk as place holders of files to be downloaded. /// </summary>> /// <param name="tc"></param> internal void CreateLocalTorrentStructure(TorrentContext tc) { if (tc is null) { throw new ArgumentNullException(nameof(tc)); } Log.Logger.Debug("Creating empty files as placeholders for downloading ..."); foreach (var file in tc.filesToDownload) { if (!System.IO.File.Exists(file.name)) { Log.Logger.Debug($"File: {file.name}"); Directory.CreateDirectory(Path.GetDirectoryName(file.name)); using (var fs = new FileStream(file.name, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None)) { fs.SetLength((Int64)file.length); } } } }
/// <summary> /// Mark torrent as fully downloaded for when in seeding from startup. This /// means that the whole of the disk image of the torrent isn't checked so /// vastly inceasing start time. /// </summary> /// <param name="tc"></param> internal void FullyDownloadedTorrentBitfield(TorrentContext tc) { if (tc is null) { throw new ArgumentNullException(nameof(tc)); } Int64 totalBytesToDownload = (Int64)tc.TotalBytesToDownload; for (UInt32 pieceNumber = 0; pieceNumber < tc.numberOfPieces; pieceNumber++) { tc.MarkPieceLocal(pieceNumber, true); tc.MarkPieceMissing(pieceNumber, false); if (totalBytesToDownload / tc.pieceLength != 0) { tc.SetPieceLength(pieceNumber, tc.pieceLength); } else { tc.SetPieceLength(pieceNumber, (UInt32)totalBytesToDownload); } totalBytesToDownload -= tc.pieceLength; } }
/// <summary> /// Loop for all pieces assembling them block by block until the download is /// complete or has been interrupted. If an an assembled piece is found to be /// corrupt it is discarded and requested again. /// </summary> /// <param name="tc"></param> /// <param name="cancelAssemblerTask"></param> private void AssembleMissingPieces(TorrentContext tc, CancellationToken cancelAssemblerTask) { UInt32 nextPiece = 0; WaitHandle[] waitHandles = new WaitHandle[] { tc.assemblyData.blockRequestsDone, cancelAssemblerTask.WaitHandle }; while (!tc.downloadFinished.WaitOne(0)) { while ((tc.NumberOfUnchokedPeers() > 0) && tc.selector.NextPiece(tc, ref nextPiece)) { if (GetPieceFromPeers(tc, nextPiece, waitHandles)) { if (tc.CheckPieceHash(nextPiece, tc.assemblyData.pieceBuffer.Buffer, tc.GetPieceLength(nextPiece))) { Log.Logger.Debug($"All blocks for piece {nextPiece} received"); tc.pieceWriteQueue.Add(tc.assemblyData.pieceBuffer); tc.MarkPieceLocal(nextPiece, true); SignalHaveToSwarm(tc, nextPiece); } } // Signal piece to be requested in unsucessful download if (!tc.IsPieceLocal(nextPiece)) { tc.MarkPieceMissing(nextPiece, true); } cancelAssemblerTask.ThrowIfCancellationRequested(); tc.paused.WaitOne(cancelAssemblerTask); } // if we reach here then no eligable peers in swarm so sleep a bit. Log.Logger.Debug($"Waiting for eligable peers to download piece from."); cancelAssemblerTask.ThrowIfCancellationRequested(); Thread.Sleep(1000); } }
/// <summary> /// Internal Tracker constructor for mock testing. /// </summary> /// <param name="tc"></param> internal Tracker(TorrentContext tc, IAnnouncerFactory announcerFactory) : this(tc) { _announcer = announcerFactory.Create(TrackerURL); }
/// <summary> /// Set torrent context and dependant fields. /// </summary> /// <param name="tc"></param> public void SetTorrentContext(TorrentContext tc) { Tc = tc ?? throw new ArgumentNullException(nameof(tc)); NumberOfMissingPieces = (int)Tc.numberOfPieces; RemotePieceBitfield = new byte[tc.Bitfield.Length]; }
/// <summary> /// Create an empty piece buffer. /// </summary> /// <param name="length">Length.</param> public PieceBuffer(TorrentContext tc, UInt32 pieceNumber, UInt32 length) : this(tc, length) { Number = pieceNumber; }
public byte[] ReadBuffer => _network?.ReadBuffer; // Network read buffer /// <summary> /// Internal constructor Setup data and resources needed by peer for unit tests. /// </summary> /// <param name="ip">Ip.</param> /// <param name="port">Port.</param> /// <param name="infoHash">Info hash.</param> /// <param name="tc">torrent context.</param> /// <param name="network">peer network layer.</param> /// internal Peer(string ip, int port, TorrentContext tc, Socket socket, IPeerNetwork network) : this(ip, port, tc, socket) { _network = network; }