Example #1
0
        private static InfoHash ComputeInfoHash(BEncoding.Dictionary info)
        {
            using (var stream = new MemoryStream())
            {
                BEncoding.Encode(info, stream);

                stream.Seek(0L, SeekOrigin.Begin);
                byte[] hash = HashHelper.ComputeSHA1(stream);
                return(new InfoHash(hash));
            }
        }
Example #2
0
        private ScrapeResponse HandleScrapeResponse(BEncoding.Dictionary info)
        {
            BEncoding.Dictionary files;
            if (!info.TryGetDictionary("files", out files))
            {
                return(null);
            }

            var torrentInfos = new List <ScrapeResponse.TorrentInfo>(files.Count);

            foreach (var torrentInfo in files)
            {
                var torrentInfoDict = (torrentInfo.Value as BEncoding.Dictionary);
                if (torrentInfoDict == null)
                {
                    continue;
                }

                long   completeCount, incompleteCount, downloadedCount;
                string torrentName;
                if (!torrentInfoDict.TryGetInteger("complete", out completeCount))
                {
                    completeCount = 0;
                }
                if (!torrentInfoDict.TryGetInteger("incomplete", out incompleteCount))
                {
                    incompleteCount = 0;
                }
                if (!torrentInfoDict.TryGetInteger("downloaded", out downloadedCount))
                {
                    downloadedCount = 0;
                }
                if (!torrentInfoDict.TryGetString("name", out torrentName))
                {
                    torrentName = null;
                }

                byte[] infoHashBytes = Encoding.UTF8.GetBytes(torrentInfo.Key); // TODO: Is this okay to do?
                var    infoHash      = new InfoHash(infoHashBytes);
                torrentInfos.Add(new ScrapeResponse.TorrentInfo(infoHash, (int)completeCount, (int)incompleteCount, (int)downloadedCount, torrentName));
            }

            return(new ScrapeResponse(this, torrentInfos.ToArray()));
        }
Example #3
0
        private AnnounceResponse HandleAnnounceResponse(BEncoding.Dictionary info)
        {
            string failureReason, warningMessage;

            if (info.TryGetString("failure reason", out failureReason))
            {
                this.failureMessage = failureReason;
            }
            if (info.TryGetString("warning message", out warningMessage))
            {
                this.warningMessage = warningMessage;
            }

            long completeCount, incompleteCount, downloadedCount, interval, minInterval;

            if (info.TryGetInteger("complete", out completeCount))
            {
                this.completeCount = (int)completeCount;
            }
            if (info.TryGetInteger("incomplete", out incompleteCount))
            {
                this.incompleteCount = (int)incompleteCount;
            }
            if (info.TryGetInteger("downloaded", out downloadedCount))
            {
                this.downloadedCount = (int)downloadedCount;
            }
            if (info.TryGetInteger("interval", out interval))
            {
                this.interval = TimeSpan.FromSeconds(interval);
            }
            if (info.TryGetInteger("min interval", out minInterval))
            {
                this.minInterval = TimeSpan.FromSeconds(minInterval);
                if (this.interval < this.minInterval)
                {
                    this.interval = this.minInterval;
                }
            }

            string trackerID;

            if (info.TryGetString("tracker id", out trackerID))
            {
                this.trackerID = trackerID;
            }

            PeerInfo[]     peerInfos = null;
            byte[]         compactPeerBytes;
            BEncoding.List peerList;
            if (info.TryGetByteArray("peers", out compactPeerBytes))
            {
                peerInfos = DecodePeers(compactPeerBytes);
            }
            else if (info.TryGetList("peers", out peerList))
            {
                peerInfos = DecodePeers(peerList);
            }

            // Decode IPv6 peers
            if (info.TryGetByteArray("peers6", out compactPeerBytes))
            {
                var peerInfosV6 = DecodePeers(compactPeerBytes);
                if (peerInfosV6 != null)
                {
                    if (peerInfos != null && peerInfos.Length > 0)
                    {
                        peerInfos = peerInfos.Concat(peerInfosV6).ToArray();
                    }
                    else
                    {
                        peerInfos = peerInfosV6;
                    }
                }
            }

            return(new AnnounceResponse(this, failureReason, warningMessage, peerInfos));
        }
Example #4
0
        private BEncoding.Dictionary Save()
        {
            var torrentInfo = new BEncoding.Dictionary();

            // Make sure that there is a piece size
            if (pieceSize == 0)
            {
                throw new InvalidOperationException("No piece size has been set yet.");
            }
            else if (!MathHelper.IsPowerOfTwo(pieceSize))
            {
                throw new InvalidOperationException("The piece size must be a power of two.");
            }
            else if (files == null || files.Length == 0)
            {
                throw new InvalidOperationException("No files have been defined yet.");
            }
            else if (totalSize == 0L)
            {
                throw new InvalidOperationException("The total size of the torrent cannot be 0.");
            }

            int pieceCount = (int)((totalSize - 1) / pieceSize) + 1;

            if (pieceCount != pieceHashes.Length)
            {
                throw new InvalidOperationException(string.Format("The calculated number of pieces is {0} while there are {1} piece hashes.", pieceCount, pieceHashes.Length));
            }

            if (announces != null && announces.Length > 0)
            {
                if (!string.IsNullOrEmpty(announces[0].Url))
                {
                    torrentInfo.Add("announce", announces[0].Url);
                }

                var trackerList = new BEncoding.List();
                for (int i = 0; i < announces.Length; i++)
                {
                    var urls = announces[i].Urls;
                    if (urls == null || urls.Length == 0)
                    {
                        continue;
                    }

                    var urlList = new BEncoding.List();
                    for (int j = 0; j < urls.Length; j++)
                    {
                        if (!string.IsNullOrEmpty(urls[j]))
                        {
                            urlList.Add(urls[j]);
                        }
                    }

                    if (urlList.Count > 0)
                    {
                        trackerList.Add(urlList);
                    }
                }

                if (trackerList.Count > 0)
                {
                    torrentInfo.Add("announce-list", trackerList);
                }
            }

            if (!string.IsNullOrEmpty(comment))
            {
                torrentInfo.Add("comment", comment);
            }
            if (!string.IsNullOrEmpty(createdBy))
            {
                torrentInfo.Add("created by", createdBy);
            }
            if (creationDate != DateTime.MinValue)
            {
                long creationDateTimestamp = TimeHelper.GetUnixTimestampFromDate(creationDate);
                torrentInfo.Add("creation date", creationDateTimestamp);
            }

            // Get the piece hash data
            byte[] pieceHashData = new byte[20 * pieceCount];
            for (int i = 0; i < pieceCount; i++)
            {
                Buffer.BlockCopy(pieceHashes[i].Hash, 0, pieceHashData, (i * 20), 20);
            }

            var info = new BEncoding.Dictionary();

            info.Add("piece length", pieceSize);
            info.Add("pieces", pieceHashData);
            info.Add("private", (isPrivate ? 1 : 0));

            if (!string.IsNullOrEmpty(source))
            {
                info.Add("source", source);
            }

            if (files != null)
            {
                if (files.Length == 1)
                {
                    var fileItem = files[0];
                    info.Add("length", fileItem.Size);

                    if (fileItem.MD5Hash != null && fileItem.MD5Hash.Length == 16)
                    {
                        string fileMD5HashHex = HexHelper.BytesToHex(fileItem.MD5Hash);
                        info.Add("md5sum", fileMD5HashHex);
                    }

                    info.Add("name", GetFileName(fileItem.Path));
                }
                else if (files.Length > 1)
                {
                    info.Add("name", name ?? "Unnamed");

                    var fileList = new BEncoding.List();
                    for (int i = 0; i < files.Length; i++)
                    {
                        var fileItem       = files[i];
                        var fileDictionary = new BEncoding.Dictionary();
                        fileDictionary.Add("length", fileItem.Size);

                        if (fileItem.MD5Hash != null && fileItem.MD5Hash.Length == 16)
                        {
                            string fileMD5HashHex = HexHelper.BytesToHex(fileItem.MD5Hash);
                            info.Add("md5sum", fileMD5HashHex);
                        }

                        string[] pathParts = fileItem.Path.Split(new char[] { '/' });
                        var      pathList  = new BEncoding.List(pathParts);
                        fileDictionary.Add("path", pathList);
                        fileList.Add(fileDictionary);
                    }

                    info.Add("files", fileList);
                }
            }

            infoHash = ComputeInfoHash(info);
            torrentInfo.Add("info", info);
            return(torrentInfo);
        }
Example #5
0
        private void Load(BEncoding.Dictionary torrentInfo)
        {
            ClearInfo();

            BEncoding.List announceList;
            if (torrentInfo.TryGetList("announce-list", out announceList))
            {
                int announceCount   = announceList.Count;
                var newAnnounceList = new List <AnnounceItem>(announceCount);
                for (int i = 0; i < announceCount; i++)
                {
                    var urlList = (announceList[i] as BEncoding.List);
                    if (urlList != null)
                    {
                        int urlCount   = urlList.Count;
                        var newUrlList = new List <string>(urlCount);
                        for (int j = 0; j < urlCount; j++)
                        {
                            string url = urlList.GetString(j);
                            if (!string.IsNullOrEmpty(url))
                            {
                                newUrlList.Add(url);
                            }
                        }

                        if (newUrlList.Count > 0)
                        {
                            newAnnounceList.Add(new AnnounceItem(newUrlList.ToArray()));
                        }
                    }
                }

                announces = newAnnounceList.ToArray();
            }
            else
            {
                string announceUrl;
                if (torrentInfo.TryGetString("announce", out announceUrl))
                {
                    announces = new AnnounceItem[]
                    {
                        new AnnounceItem(announceUrl)
                    };
                }
            }

            long creationDateTimestamp;

            if (!torrentInfo.TryGetString("comment", out comment))
            {
                comment = null;
            }
            if (!torrentInfo.TryGetString("created by", out createdBy))
            {
                createdBy = null;
            }
            if (torrentInfo.TryGetInteger("creation date", out creationDateTimestamp))
            {
                creationDate = TimeHelper.GetDateFromUnixTimestamp(creationDateTimestamp);
            }
            else
            {
                creationDate = DateTime.MinValue;
            }

            BEncoding.Dictionary info;
            if (!torrentInfo.TryGetDictionary("info", out info))
            {
                throw new InvalidDataException("The info dictionary is missing in the torrent meta-data.");
            }

            infoHash = ComputeInfoHash(info);

            long pieceSize;

            if (!info.TryGetInteger("piece length", out pieceSize))
            {
                throw new InvalidDataException("The piece length is missing in the torrent meta-data. But it is required for all torrents.");
            }

            byte[] pieceHashData;
            if (!info.TryGetByteArray("pieces", out pieceHashData))
            {
                throw new InvalidDataException("The piece hashes are missing in the torrent meta-data. But it is required for all torrents.");
            }
            else if ((pieceHashData.Length % 20) != 0)
            {
                throw new InvalidDataException("The piece hashes are invalid in the torrent meta-data. It must be a multiple of 20 (for SHA1 hashes).");
            }

            int pieceCount = (pieceHashData.Length / 20);

            pieceHashes = new PieceHash[pieceCount];
            for (int i = 0; i < pieceCount; i++)
            {
                byte[] pieceHashBytes = new byte[20];
                Buffer.BlockCopy(pieceHashData, (i * 20), pieceHashBytes, 0, 20);
                pieceHashes[i] = new PieceHash(pieceHashBytes);
            }

            long isPrivate;

            if (info.TryGetInteger("private", out isPrivate) && isPrivate == 1)
            {
                this.isPrivate = true;
            }

            if (!info.TryGetString("source", out source))
            {
                source = null;
            }

            if (!info.TryGetString("name", out name) || string.IsNullOrEmpty(name))
            {
                throw new InvalidDataException("The name string is missing in the torrent meta-data. But it is required for all torrents.");
            }

            this.pieceSize = (int)pieceSize;

            BEncoding.List fileList;
            if (info.TryGetList("files", out fileList))
            {
                int fileCount = fileList.Count;
                files = new FileItem[fileCount];
                for (int i = 0; i < fileCount; i++)
                {
                    var fileItem = fileList[i] as BEncoding.Dictionary;
                    if (fileItem == null)
                    {
                        throw new InvalidDataException("The format of the torrent meta-data is invalid. Expected a dictionary of file information.");
                    }

                    long fileSize;
                    if (!fileItem.TryGetInteger("length", out fileSize))
                    {
                        throw new InvalidDataException("The format of the torrent meta-data is invalid. Expected a file size.");
                    }

                    string fileMD5HashHex;
                    byte[] fileMD5Hash = null;
                    if (fileItem.TryGetString("md5sum", out fileMD5HashHex) && fileMD5HashHex.Length == 32)
                    {
                        fileMD5Hash = HexHelper.HexToBytes(fileMD5HashHex);
                    }

                    BEncoding.List pathList;
                    if (!fileItem.TryGetList("path", out pathList))
                    {
                        throw new InvalidDataException("The format of the torrent meta-data is invalid. Expected a file path.");
                    }

                    int      pathPartCount = pathList.Count;
                    string[] pathParts     = new string[pathPartCount];
                    for (int j = 0; j < pathPartCount; j++)
                    {
                        string pathPart = pathList.GetString(j);
                        if (string.IsNullOrEmpty(pathPart))
                        {
                            throw new InvalidDataException("The format of the torrent meta-data is invalid. Expected a file path.");
                        }

                        pathParts[j] = pathPart;
                    }

                    string filePath = string.Join("/", pathParts);
                    files[i] = new FileItem(filePath, fileSize, fileMD5Hash);
                }
            }
            else
            {
                long fileSize;
                if (!info.TryGetInteger("length", out fileSize))
                {
                    throw new InvalidDataException("The file length is missing in the torrent meta-data. But it is required for single-file torrents.");
                }

                string fileMD5HashHex;
                byte[] fileMD5Hash = null;
                if (info.TryGetString("md5sum", out fileMD5HashHex) && fileMD5HashHex.Length == 32)
                {
                    fileMD5Hash = HexHelper.HexToBytes(fileMD5HashHex);
                }

                files = new FileItem[]
                {
                    new FileItem(name, fileSize, fileMD5Hash)
                };
            }

            totalSize = ComputeTotalSize();
            int expectedPieceCount = (int)((totalSize - 1) / pieceSize) + 1;

            if (expectedPieceCount != pieceHashes.Length)
            {
                throw new InvalidOperationException(string.Format("The calculated number of pieces is {0} while there are {1} piece hashes.", expectedPieceCount, pieceHashes.Length));
            }
        }