bt

module
v0.4.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 8, 2021 License: Apache-2.0

README

BT - Another Implementation Based On Golang Build Status GoDoc License

A pure golang implementation of BitTorrent library, which is inspired by dht and torrent.

Install

$ go get -u github.com/xgfone/bt

Features

  • Support Go1.9+.
  • Support IPv4/IPv6.
  • Multi-BEPs implementation.
  • Pure Go implementation without CGO.
  • Only library without any denpendencies. For the command tools, see bttools.

The Implemented Specifications

Example

See godoc or bttools.

Example 1: Download the file from the remote peer
package main

import (
	"context"
	"flag"
	"fmt"
	"io"
	"log"
	"net/url"
	"os"
	"time"

	"github.com/xgfone/bt/downloader"
	"github.com/xgfone/bt/metainfo"
	pp "github.com/xgfone/bt/peerprotocol"
	"github.com/xgfone/bt/tracker"
)

var peeraddr string

func init() {
	flag.StringVar(&peeraddr, "peeraddr", "", "The address of the peer storing the file.")
}

func getPeersFromTrackers(id, infohash metainfo.Hash, trackers []string) (peers []string) {
	c, cancel := context.WithTimeout(context.Background(), time.Second*3)
	defer cancel()

	resp := tracker.GetPeers(c, id, infohash, trackers)
	for r := range resp {
		for _, addr := range r.Resp.Addresses {
			addrs := addr.String()
			nonexist := true
			for _, peer := range peers {
				if peer == addrs {
					nonexist = false
					break
				}
			}

			if nonexist {
				peers = append(peers, addrs)
			}
		}
	}

	return
}

func main() {
	flag.Parse()

	torrentfile := os.Args[1]
	mi, err := metainfo.LoadFromFile(torrentfile)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	id := metainfo.NewRandomHash()
	infohash := mi.InfoHash()
	info, err := mi.Info()
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	var peers []string
	if peeraddr != "" {
		peers = []string{peeraddr}
	} else {
		// Get the peers from the trackers in the torrent file.
		trackers := mi.Announces().Unique()
		if len(trackers) == 0 {
			fmt.Println("no trackers")
			return
		}

		peers = getPeersFromTrackers(id, infohash, trackers)
		if len(peers) == 0 {
			fmt.Println("no peers")
			return
		}
	}

	// We save the downloaded file to the current directory.
	w := metainfo.NewWriter("", info, 0)
	defer w.Close()

	// We don't request the blocks from the remote peers concurrently,
	// and it is only an example. But you can do it concurrently.
	dm := newDownloadManager(w, info)
	for peerslen := len(peers); peerslen > 0 && !dm.IsFinished(); {
		peerslen--
		peer := peers[peerslen]
		peers = peers[:peerslen]
		downloadFileFromPeer(peer, id, infohash, dm)
	}
}

func downloadFileFromPeer(peer string, id, infohash metainfo.Hash, dm *downloadManager) {
	pc, err := pp.NewPeerConnByDial(peer, id, infohash, time.Second*3)
	if err != nil {
		log.Printf("fail to dial '%s'", peer)
		return
	}
	defer pc.Close()

	dm.doing = false
	pc.Timeout = time.Second * 10
	if err = pc.Handshake(); err != nil {
		log.Printf("fail to handshake with '%s': %s", peer, err)
		return
	}

	info := dm.writer.Info()
	bdh := downloader.NewBlockDownloadHandler(info, dm.OnBlock, dm.RequestBlock)
	if err = bdh.OnHandShake(pc); err != nil {
		log.Printf("handshake error with '%s': %s", peer, err)
		return
	}

	var msg pp.Message
	for !dm.IsFinished() {
		switch msg, err = pc.ReadMsg(); err {
		case nil:
			switch err = pc.HandleMessage(msg, bdh); err {
			case nil, pp.ErrChoked:
			default:
				log.Printf("fail to handle the msg from '%s': %s", peer, err)
				return
			}
		case io.EOF:
			log.Printf("got EOF from '%s'", peer)
			return
		default:
			log.Printf("fail to read the msg from '%s': %s", peer, err)
			return
		}
	}
}

func newDownloadManager(w metainfo.Writer, info metainfo.Info) *downloadManager {
	length := info.Piece(0).Length()
	return &downloadManager{writer: w, plength: length}
}

type downloadManager struct {
	writer  metainfo.Writer
	pindex  uint32
	poffset uint32
	plength int64
	doing   bool
}

func (dm *downloadManager) IsFinished() bool {
	if dm.pindex >= uint32(dm.writer.Info().CountPieces()) {
		return true
	}
	return false
}

func (dm *downloadManager) OnBlock(index, offset uint32, b []byte) (err error) {
	if dm.pindex != index {
		return fmt.Errorf("inconsistent piece: old=%d, new=%d", dm.pindex, index)
	} else if dm.poffset != offset {
		return fmt.Errorf("inconsistent offset for piece '%d': old=%d, new=%d",
			index, dm.poffset, offset)
	}

	dm.doing = false
	n, err := dm.writer.WriteBlock(index, offset, b)
	if err == nil {
		dm.poffset = offset + uint32(n)
		dm.plength -= int64(n)
	}
	return
}

func (dm *downloadManager) RequestBlock(pc *pp.PeerConn) (err error) {
	if dm.doing {
		return
	}

	if dm.plength <= 0 {
		dm.pindex++
		if dm.IsFinished() {
			return
		}

		dm.poffset = 0
		dm.plength = dm.writer.Info().Piece(int(dm.pindex)).Length()
	}

	index := dm.pindex
	begin := dm.poffset
	length := uint32(downloader.BlockSize)
	if length > uint32(dm.plength) {
		length = uint32(dm.plength)
	}

	log.Printf("Request Block from '%s': index=%d, offset=%d, length=%d",
		pc.RemoteAddr().String(), index, begin, length)
	if err = pc.SendRequest(index, begin, length); err == nil {
		dm.doing = true
	}
	return
}

Directories

Path Synopsis
Package bencode implements encoding and decoding of bencoded objects, which has a similar API to the encoding/json package and many other serialization formats.
Package bencode implements encoding and decoding of bencoded objects, which has a similar API to the encoding/json package and many other serialization formats.
Package dht implements the DHT Protocol.
Package dht implements the DHT Protocol.
Package downloader is used to download the torrent or the real file from the peer node by the peer wire protocol.
Package downloader is used to download the torrent or the real file from the peer node by the peer wire protocol.
Package krpc supplies the KRPC message used by DHT.
Package krpc supplies the KRPC message used by DHT.
Package metainfo is used to encode or decode the metainfo of the torrent file and the MagNet link.
Package metainfo is used to encode or decode the metainfo of the torrent file and the MagNet link.
Package peerprotocol implements the core BT peer protocol.
Package peerprotocol implements the core BT peer protocol.
Package tracker supplies some common type interfaces of the BT tracker protocol.
Package tracker supplies some common type interfaces of the BT tracker protocol.
httptracker
Package httptracker implements the tracker protocol based on HTTP/HTTPS.
Package httptracker implements the tracker protocol based on HTTP/HTTPS.
udptracker
Package udptracker implements the tracker protocol based on UDP.
Package udptracker implements the tracker protocol based on UDP.
Package utils supplies some convenient functions.
Package utils supplies some convenient functions.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL