Begin adding magnet and ut_metadata support
This commit is contained in:
parent
33d53cf9e5
commit
49e71f9654
266
client.go
266
client.go
|
@ -19,8 +19,10 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"container/list"
|
"container/list"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"crypto/sha1"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/nsf/libtorgo/bencode"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
mathRand "math/rand"
|
mathRand "math/rand"
|
||||||
|
@ -57,6 +59,9 @@ func (me *Client) PrioritizeDataRegion(ih InfoHash, off, len_ int64) error {
|
||||||
if t == nil {
|
if t == nil {
|
||||||
return errors.New("no such active torrent")
|
return errors.New("no such active torrent")
|
||||||
}
|
}
|
||||||
|
if t.Info == nil {
|
||||||
|
return errors.New("missing metadata")
|
||||||
|
}
|
||||||
newPriorities := make([]request, 0, (len_+chunkSize-1)/chunkSize)
|
newPriorities := make([]request, 0, (len_+chunkSize-1)/chunkSize)
|
||||||
for len_ > 0 {
|
for len_ > 0 {
|
||||||
req, ok := t.offsetRequest(off)
|
req, ok := t.offsetRequest(off)
|
||||||
|
@ -113,7 +118,7 @@ func (cl *Client) WriteStatus(w io.Writer) {
|
||||||
cl.mu.Lock()
|
cl.mu.Lock()
|
||||||
defer cl.mu.Unlock()
|
defer cl.mu.Unlock()
|
||||||
for _, t := range cl.torrents {
|
for _, t := range cl.torrents {
|
||||||
fmt.Fprintf(w, "%s: %f%%\n", t.MetaInfo.Name, 100*(1-float32(t.BytesLeft())/float32(t.Length())))
|
fmt.Fprintf(w, "%s: %f%%\n", t.Name(), 100*(1-float32(t.BytesLeft())/float32(t.Length())))
|
||||||
t.WriteStatus(w)
|
t.WriteStatus(w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,7 +133,7 @@ func (cl *Client) TorrentReadAt(ih InfoHash, off int64, p []byte) (n int, err er
|
||||||
err = errors.New("unknown torrent")
|
err = errors.New("unknown torrent")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
index := pp.Integer(off / t.MetaInfo.PieceLength)
|
index := pp.Integer(off / t.Info.PieceLength())
|
||||||
// Reading outside the bounds of a file is an error.
|
// Reading outside the bounds of a file is an error.
|
||||||
if index < 0 {
|
if index < 0 {
|
||||||
err = os.ErrInvalid
|
err = os.ErrInvalid
|
||||||
|
@ -287,7 +292,7 @@ func (me *Client) runConnection(sock net.Conn, torrent *torrent) (err error) {
|
||||||
PeerChoked: true,
|
PeerChoked: true,
|
||||||
write: make(chan []byte),
|
write: make(chan []byte),
|
||||||
post: make(chan pp.Message),
|
post: make(chan pp.Message),
|
||||||
PeerMaxRequests: 250,
|
PeerMaxRequests: 64,
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
// There's a lock and deferred unlock later in this function. The
|
// There's a lock and deferred unlock later in this function. The
|
||||||
|
@ -299,7 +304,7 @@ func (me *Client) runConnection(sock net.Conn, torrent *torrent) (err error) {
|
||||||
go conn.writer()
|
go conn.writer()
|
||||||
// go conn.writeOptimizer()
|
// go conn.writeOptimizer()
|
||||||
conn.write <- pp.Bytes(pp.Protocol)
|
conn.write <- pp.Bytes(pp.Protocol)
|
||||||
conn.write <- pp.Bytes("\x00\x00\x00\x00\x00\x00\x00\x00")
|
conn.write <- pp.Bytes("\x00\x00\x00\x00\x00\x10\x00\x00")
|
||||||
if torrent != nil {
|
if torrent != nil {
|
||||||
conn.write <- pp.Bytes(torrent.InfoHash[:])
|
conn.write <- pp.Bytes(torrent.InfoHash[:])
|
||||||
conn.write <- pp.Bytes(me.PeerId[:])
|
conn.write <- pp.Bytes(me.PeerId[:])
|
||||||
|
@ -344,6 +349,23 @@ func (me *Client) runConnection(sock net.Conn, torrent *torrent) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
go conn.writeOptimizer(time.Minute)
|
go conn.writeOptimizer(time.Minute)
|
||||||
|
if conn.PeerExtensions[5]&0x10 != 0 {
|
||||||
|
conn.Post(pp.Message{
|
||||||
|
Type: pp.Extended,
|
||||||
|
ExtendedID: pp.HandshakeExtendedID,
|
||||||
|
ExtendedPayload: func() []byte {
|
||||||
|
b, err := bencode.Marshal(map[string]interface{}{
|
||||||
|
"m": map[string]int{
|
||||||
|
"ut_metadata": 1,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}(),
|
||||||
|
})
|
||||||
|
}
|
||||||
if torrent.haveAnyPieces() {
|
if torrent.haveAnyPieces() {
|
||||||
conn.Post(pp.Message{
|
conn.Post(pp.Message{
|
||||||
Type: pp.Bitfield,
|
Type: pp.Bitfield,
|
||||||
|
@ -358,13 +380,13 @@ func (me *Client) runConnection(sock net.Conn, torrent *torrent) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (me *Client) peerGotPiece(torrent *torrent, conn *connection, piece int) {
|
func (me *Client) peerGotPiece(t *torrent, c *connection, piece int) {
|
||||||
if conn.PeerPieces == nil {
|
for piece >= len(c.PeerPieces) {
|
||||||
conn.PeerPieces = make([]bool, len(torrent.Pieces))
|
c.PeerPieces = append(c.PeerPieces, false)
|
||||||
}
|
}
|
||||||
conn.PeerPieces[piece] = true
|
c.PeerPieces[piece] = true
|
||||||
if torrent.wantPiece(piece) {
|
if t.wantPiece(piece) {
|
||||||
me.replenishConnRequests(torrent, conn)
|
me.replenishConnRequests(t, c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -388,6 +410,31 @@ func (cl *Client) connDeleteRequest(t *torrent, cn *connection, r request) {
|
||||||
delete(cn.Requests, r)
|
delete(cn.Requests, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cl *Client) requestPendingMetadata(t *torrent, c *connection) {
|
||||||
|
var pending []int
|
||||||
|
for index, have := range t.MetaDataHave {
|
||||||
|
if !have {
|
||||||
|
pending = append(pending, index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, i := range mathRand.Perm(len(pending)) {
|
||||||
|
c.Post(pp.Message{
|
||||||
|
Type: pp.Extended,
|
||||||
|
ExtendedID: byte(c.PeerExtensionIDs["ut_metadata"]),
|
||||||
|
ExtendedPayload: func() []byte {
|
||||||
|
b, err := bencode.Marshal(map[string]int{
|
||||||
|
"msg_type": 0,
|
||||||
|
"piece": pending[i],
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (me *Client) connectionLoop(t *torrent, c *connection) error {
|
func (me *Client) connectionLoop(t *torrent, c *connection) error {
|
||||||
decoder := pp.Decoder{
|
decoder := pp.Decoder{
|
||||||
R: bufio.NewReader(c.Socket),
|
R: bufio.NewReader(c.Socket),
|
||||||
|
@ -455,15 +502,18 @@ func (me *Client) connectionLoop(t *torrent, c *connection) error {
|
||||||
log.Printf("received unexpected cancel: %v", req)
|
log.Printf("received unexpected cancel: %v", req)
|
||||||
}
|
}
|
||||||
case pp.Bitfield:
|
case pp.Bitfield:
|
||||||
if len(msg.Bitfield) < t.NumPieces() {
|
|
||||||
err = errors.New("received invalid bitfield")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if c.PeerPieces != nil {
|
if c.PeerPieces != nil {
|
||||||
err = errors.New("received unexpected bitfield")
|
err = errors.New("received unexpected bitfield")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
c.PeerPieces = msg.Bitfield[:t.NumPieces()]
|
if t.haveInfo() {
|
||||||
|
if len(msg.Bitfield) < t.NumPieces() {
|
||||||
|
err = errors.New("received invalid bitfield")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
msg.Bitfield = msg.Bitfield[:t.NumPieces()]
|
||||||
|
}
|
||||||
|
c.PeerPieces = msg.Bitfield
|
||||||
for index, has := range c.PeerPieces {
|
for index, has := range c.PeerPieces {
|
||||||
if has {
|
if has {
|
||||||
me.peerGotPiece(t, c, index)
|
me.peerGotPiece(t, c, index)
|
||||||
|
@ -471,6 +521,79 @@ func (me *Client) connectionLoop(t *torrent, c *connection) error {
|
||||||
}
|
}
|
||||||
case pp.Piece:
|
case pp.Piece:
|
||||||
err = me.downloadedChunk(t, c, &msg)
|
err = me.downloadedChunk(t, c, &msg)
|
||||||
|
case pp.Extended:
|
||||||
|
switch msg.ExtendedID {
|
||||||
|
case pp.HandshakeExtendedID:
|
||||||
|
var d map[string]interface{}
|
||||||
|
err = bencode.Unmarshal(msg.ExtendedPayload, &d)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("error decoding extended message payload: %s", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
m, ok := d["m"]
|
||||||
|
if !ok {
|
||||||
|
err = errors.New("handshake missing m item")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
mTyped, ok := m.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
err = errors.New("handshake m value is not dict")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if c.PeerExtensionIDs == nil {
|
||||||
|
c.PeerExtensionIDs = make(map[string]int64, len(mTyped))
|
||||||
|
}
|
||||||
|
for name, v := range mTyped {
|
||||||
|
id, ok := v.(int64)
|
||||||
|
if !ok {
|
||||||
|
log.Printf("bad handshake m item extension ID type: %T", v)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if id == 0 {
|
||||||
|
delete(c.PeerExtensionIDs, name)
|
||||||
|
} else {
|
||||||
|
c.PeerExtensionIDs[name] = id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
metadata_sizeUntyped, ok := d["metadata_size"]
|
||||||
|
if ok {
|
||||||
|
metadata_size, ok := metadata_sizeUntyped.(int64)
|
||||||
|
if !ok {
|
||||||
|
log.Printf("bad metadata_size type: %T", metadata_sizeUntyped)
|
||||||
|
} else {
|
||||||
|
log.Printf("metadata_size: %d", metadata_size)
|
||||||
|
t.SetMetaDataSize(metadata_size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, ok := c.PeerExtensionIDs["ut_metadata"]; ok {
|
||||||
|
me.requestPendingMetadata(t, c)
|
||||||
|
}
|
||||||
|
case 1:
|
||||||
|
var d map[string]int
|
||||||
|
err := bencode.Unmarshal(msg.ExtendedPayload, &d)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("error unmarshalling extended payload: %s", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if d["msg_type"] != 1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
piece := d["piece"]
|
||||||
|
log.Println(piece, d["total_size"], len(msg.ExtendedPayload))
|
||||||
|
copy(t.MetaData[(1<<14)*piece:], msg.ExtendedPayload[len(msg.ExtendedPayload)-metadataPieceSize(d["total_size"], piece):])
|
||||||
|
t.MetaDataHave[piece] = true
|
||||||
|
if !t.GotAllMetadataPieces() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
log.Printf("%q", t.MetaData)
|
||||||
|
h := sha1.New()
|
||||||
|
h.Write(t.MetaData)
|
||||||
|
var ih InfoHash
|
||||||
|
copy(ih[:], h.Sum(nil)[:])
|
||||||
|
if ih != t.InfoHash {
|
||||||
|
panic(ih)
|
||||||
|
}
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("received unknown message type: %#v", msg.Type)
|
err = fmt.Errorf("received unknown message type: %#v", msg.Type)
|
||||||
}
|
}
|
||||||
|
@ -539,33 +662,45 @@ func (me *Client) AddPeers(infoHash InfoHash, peers []Peer) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare a Torrent without any attachment to a Client. That means we can
|
func (cl *Client) setMetaData(t *torrent, md MetaData) (err error) {
|
||||||
// initialize fields all fields that don't require the Client without locking
|
t.Data, err = mmapTorrentData(md, cl.DataDir)
|
||||||
// it.
|
|
||||||
func newTorrent(metaInfo *metainfo.MetaInfo, dataDir string) (t *torrent, err error) {
|
|
||||||
t = &torrent{
|
|
||||||
InfoHash: BytesInfoHash(metaInfo.InfoHash),
|
|
||||||
MetaInfo: metaInfo,
|
|
||||||
}
|
|
||||||
t.Data, err = mmapTorrentData(metaInfo, dataDir)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for offset := 0; offset < len(metaInfo.Pieces); offset += pieceHash.Size() {
|
for _, hash := range md.PieceHashes() {
|
||||||
hash := metaInfo.Pieces[offset : offset+pieceHash.Size()]
|
|
||||||
if len(hash) != pieceHash.Size() {
|
|
||||||
err = errors.New("bad piece hash in metainfo")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
piece := &piece{}
|
piece := &piece{}
|
||||||
copyHashSum(piece.Hash[:], hash)
|
copyHashSum(piece.Hash[:], []byte(hash))
|
||||||
t.Pieces = append(t.Pieces, piece)
|
t.Pieces = append(t.Pieces, piece)
|
||||||
t.pendAllChunkSpecs(pp.Integer(len(t.Pieces) - 1))
|
t.pendAllChunkSpecs(pp.Integer(len(t.Pieces) - 1))
|
||||||
}
|
}
|
||||||
t.Trackers = make([][]tracker.Client, len(metaInfo.AnnounceList))
|
t.Priorities = list.New()
|
||||||
for tierIndex := range metaInfo.AnnounceList {
|
|
||||||
|
// Queue all pieces for hashing. This is done sequentially to avoid
|
||||||
|
// spamming goroutines.
|
||||||
|
for _, p := range t.Pieces {
|
||||||
|
p.QueuedForHash = true
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
for i := range t.Pieces {
|
||||||
|
cl.verifyPiece(t, pp.Integer(i))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
cl.DownloadStrategy.TorrentStarted(t)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare a Torrent without any attachment to a Client. That means we can
|
||||||
|
// initialize fields all fields that don't require the Client without locking
|
||||||
|
// it.
|
||||||
|
func newTorrent(ih InfoHash, announceList [][]string) (t *torrent, err error) {
|
||||||
|
t = &torrent{
|
||||||
|
InfoHash: ih,
|
||||||
|
}
|
||||||
|
t.Trackers = make([][]tracker.Client, len(announceList))
|
||||||
|
for tierIndex := range announceList {
|
||||||
tier := t.Trackers[tierIndex]
|
tier := t.Trackers[tierIndex]
|
||||||
for _, url := range metaInfo.AnnounceList[tierIndex] {
|
for _, url := range announceList[tierIndex] {
|
||||||
tr, err := tracker.New(url)
|
tr, err := tracker.New(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
|
@ -585,36 +720,52 @@ func newTorrent(metaInfo *metainfo.MetaInfo, dataDir string) (t *torrent, err er
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds the torrent to the client.
|
func (cl *Client) AddMagnet(uri string) (err error) {
|
||||||
func (me *Client) AddTorrent(metaInfo *metainfo.MetaInfo) error {
|
m, err := ParseMagnetURI(uri)
|
||||||
torrent, err := newTorrent(metaInfo, me.DataDir)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
me.mu.Lock()
|
t, err := newTorrent(m.InfoHash, [][]string{m.Trackers})
|
||||||
defer me.mu.Unlock()
|
if err != nil {
|
||||||
if _, ok := me.torrents[torrent.InfoHash]; ok {
|
return
|
||||||
return torrent.Close()
|
|
||||||
}
|
}
|
||||||
me.torrents[torrent.InfoHash] = torrent
|
t.DisplayName = m.DisplayName
|
||||||
me.DownloadStrategy.TorrentStarted(torrent)
|
cl.mu.Lock()
|
||||||
|
defer cl.mu.Unlock()
|
||||||
|
err = cl.addTorrent(t)
|
||||||
|
if err != nil {
|
||||||
|
t.Close()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (me *Client) addTorrent(t *torrent) (err error) {
|
||||||
|
if _, ok := me.torrents[t.InfoHash]; ok {
|
||||||
|
err = fmt.Errorf("torrent infohash collision")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
me.torrents[t.InfoHash] = t
|
||||||
if !me.DisableTrackers {
|
if !me.DisableTrackers {
|
||||||
go me.announceTorrent(torrent)
|
go me.announceTorrent(t)
|
||||||
}
|
}
|
||||||
torrent.Priorities = list.New()
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Queue all pieces for hashing. This is done sequentially to avoid
|
// Adds the torrent to the client.
|
||||||
// spamming goroutines.
|
func (me *Client) AddTorrent(metaInfo *metainfo.MetaInfo) (err error) {
|
||||||
for _, p := range torrent.Pieces {
|
t, err := newTorrent(BytesInfoHash(metaInfo.InfoHash), metaInfo.AnnounceList)
|
||||||
p.QueuedForHash = true
|
if err != nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
go func() {
|
err = me.addTorrent(t)
|
||||||
for i := range torrent.Pieces {
|
if err != nil {
|
||||||
me.verifyPiece(torrent, pp.Integer(i))
|
return
|
||||||
}
|
}
|
||||||
}()
|
err = me.setMetaData(t, metaInfoMetaData{metaInfo})
|
||||||
|
if err != nil {
|
||||||
return nil
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *Client) listenerAnnouncePort() (port int16) {
|
func (cl *Client) listenerAnnouncePort() (port int16) {
|
||||||
|
@ -862,6 +1013,7 @@ func (me *Client) downloadedChunk(t *torrent, c *connection, msg *pp.Message) er
|
||||||
|
|
||||||
// Do we actually want this chunk?
|
// Do we actually want this chunk?
|
||||||
if _, ok := t.Pieces[req.Index].PendingChunkSpecs[req.chunkSpec]; !ok {
|
if _, ok := t.Pieces[req.Index].PendingChunkSpecs[req.chunkSpec]; !ok {
|
||||||
|
log.Printf("got unnecessary chunk from %v: %q", req, string(c.PeerId[:]))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -81,7 +81,7 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveTable() error {
|
func saveTable() error {
|
||||||
goodNodes := s.GoodNodes()
|
goodNodes := s.Nodes()
|
||||||
if *tableFileName == "" {
|
if *tableFileName == "" {
|
||||||
if len(goodNodes) != 0 {
|
if len(goodNodes) != 0 {
|
||||||
log.Printf("discarding %d good nodes!", len(goodNodes))
|
log.Printf("discarding %d good nodes!", len(goodNodes))
|
||||||
|
@ -123,6 +123,8 @@ func main() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("error bootstrapping: %s", err)
|
log.Printf("error bootstrapping: %s", err)
|
||||||
s.StopServing()
|
s.StopServing()
|
||||||
|
} else {
|
||||||
|
log.Print("bootstrapping complete")
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
err := s.Serve()
|
err := s.Serve()
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
_ "net/http/pprof"
|
_ "net/http/pprof"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
metainfo "github.com/nsf/libtorgo/torrent"
|
metainfo "github.com/nsf/libtorgo/torrent"
|
||||||
|
|
||||||
|
@ -53,16 +54,30 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, arg := range flag.Args() {
|
for _, arg := range flag.Args() {
|
||||||
metaInfo, err := metainfo.LoadFromFile(arg)
|
var ih torrent.InfoHash
|
||||||
if err != nil {
|
if strings.HasPrefix(arg, "magnet:") {
|
||||||
log.Fatal(err)
|
m, err := torrent.ParseMagnetURI(arg)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error parsing magnet uri: %s", err)
|
||||||
|
}
|
||||||
|
ih = m.InfoHash
|
||||||
|
err = client.AddMagnet(arg)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error adding magnet: %s", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
metaInfo, err := metainfo.LoadFromFile(arg)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
err = client.AddTorrent(metaInfo)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
ih = torrent.BytesInfoHash(metaInfo.InfoHash)
|
||||||
}
|
}
|
||||||
err = client.AddTorrent(metaInfo)
|
client.PrioritizeDataRegion(ih, 0, 999999999)
|
||||||
if err != nil {
|
err := client.AddPeers(ih, func() []torrent.Peer {
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
client.PrioritizeDataRegion(torrent.BytesInfoHash(metaInfo.InfoHash), 0, 999999999)
|
|
||||||
err = client.AddPeers(torrent.BytesInfoHash(metaInfo.InfoHash), func() []torrent.Peer {
|
|
||||||
if *testPeer == "" {
|
if *testPeer == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,13 +27,14 @@ type connection struct {
|
||||||
Requests map[request]struct{}
|
Requests map[request]struct{}
|
||||||
|
|
||||||
// Stuff controlled by the remote peer.
|
// Stuff controlled by the remote peer.
|
||||||
PeerId [20]byte
|
PeerId [20]byte
|
||||||
PeerInterested bool
|
PeerInterested bool
|
||||||
PeerChoked bool
|
PeerChoked bool
|
||||||
PeerRequests map[request]struct{}
|
PeerRequests map[request]struct{}
|
||||||
PeerExtensions [8]byte
|
PeerExtensions [8]byte
|
||||||
PeerPieces []bool
|
PeerPieces []bool
|
||||||
PeerMaxRequests int // Maximum pending requests the peer allows.
|
PeerMaxRequests int // Maximum pending requests the peer allows.
|
||||||
|
PeerExtensionIDs map[string]int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cn *connection) completedString() string {
|
func (cn *connection) completedString() string {
|
||||||
|
|
27
dht/dht.go
27
dht/dht.go
|
@ -56,7 +56,18 @@ type transaction struct {
|
||||||
response chan Msg
|
response chan Msg
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) setDefaults() {
|
func (s *Server) setDefaults() (err error) {
|
||||||
|
if s.Socket == nil {
|
||||||
|
var addr *net.UDPAddr
|
||||||
|
addr, err = net.ResolveUDPAddr("", ":6882")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.Socket, err = net.ListenUDP("udp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
if s.ID == "" {
|
if s.ID == "" {
|
||||||
var id [20]byte
|
var id [20]byte
|
||||||
h := crypto.SHA1.New()
|
h := crypto.SHA1.New()
|
||||||
|
@ -74,10 +85,12 @@ func (s *Server) setDefaults() {
|
||||||
}
|
}
|
||||||
s.ID = string(id[:])
|
s.ID = string(id[:])
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Init() {
|
func (s *Server) Init() error {
|
||||||
s.setDefaults()
|
return s.setDefaults()
|
||||||
|
//s.nodes = make(map[string]*Node)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Serve() error {
|
func (s *Server) Serve() error {
|
||||||
|
@ -401,13 +414,13 @@ func (s *Server) Bootstrap() (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) GoodNodes() (nis []NodeInfo) {
|
func (s *Server) Nodes() (nis []NodeInfo) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
for _, node := range s.nodes {
|
for _, node := range s.nodes {
|
||||||
if !node.Good() {
|
// if !node.Good() {
|
||||||
continue
|
// continue
|
||||||
}
|
// }
|
||||||
ni := NodeInfo{
|
ni := NodeInfo{
|
||||||
Addr: node.addr,
|
Addr: node.addr,
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
package torrent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base32"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Magnet struct {
|
||||||
|
InfoHash [20]byte
|
||||||
|
Trackers []string
|
||||||
|
DisplayName string
|
||||||
|
}
|
||||||
|
|
||||||
|
const xtPrefix = "urn:btih:"
|
||||||
|
|
||||||
|
func ParseMagnetURI(uri string) (m Magnet, err error) {
|
||||||
|
u, err := url.Parse(uri)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("error parsing uri: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
xt := u.Query().Get("xt")
|
||||||
|
if !strings.HasPrefix(xt, xtPrefix) {
|
||||||
|
err = fmt.Errorf("bad xt parameter")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
xt = xt[len(xtPrefix):]
|
||||||
|
decode := func() func(dst, src []byte) (int, error) {
|
||||||
|
switch len(xt) {
|
||||||
|
case 40:
|
||||||
|
return hex.Decode
|
||||||
|
case 32:
|
||||||
|
return base32.StdEncoding.Decode
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if decode == nil {
|
||||||
|
err = fmt.Errorf("unhandled xt parameter encoding")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n, err := decode(m.InfoHash[:], []byte(xt))
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("error decoding xt: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if n != 20 {
|
||||||
|
panic(n)
|
||||||
|
}
|
||||||
|
m.DisplayName = u.Query().Get("dn")
|
||||||
|
m.Trackers = u.Query()["tr"]
|
||||||
|
return
|
||||||
|
}
|
42
misc.go
42
misc.go
|
@ -4,13 +4,13 @@ import (
|
||||||
"bitbucket.org/anacrolix/go.torrent/mmap_span"
|
"bitbucket.org/anacrolix/go.torrent/mmap_span"
|
||||||
"crypto"
|
"crypto"
|
||||||
"errors"
|
"errors"
|
||||||
|
metainfo "github.com/nsf/libtorgo/torrent"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"bitbucket.org/anacrolix/go.torrent/peer_protocol"
|
"bitbucket.org/anacrolix/go.torrent/peer_protocol"
|
||||||
metainfo "github.com/nsf/libtorgo/torrent"
|
|
||||||
"launchpad.net/gommap"
|
"launchpad.net/gommap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -103,15 +103,41 @@ var (
|
||||||
ErrDataNotReady = errors.New("data not ready")
|
ErrDataNotReady = errors.New("data not ready")
|
||||||
)
|
)
|
||||||
|
|
||||||
func mmapTorrentData(metaInfo *metainfo.MetaInfo, location string) (mms mmap_span.MMapSpan, err error) {
|
type metaInfoMetaData struct {
|
||||||
|
mi *metainfo.MetaInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (me metaInfoMetaData) Files() []metainfo.FileInfo { return me.mi.Files }
|
||||||
|
func (me metaInfoMetaData) Name() string { return me.mi.Name }
|
||||||
|
func (me metaInfoMetaData) PieceHashes() []string {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (me metaInfoMetaData) PieceLength() int64 { return me.mi.PieceLength }
|
||||||
|
func (me metaInfoMetaData) PieceCount() int {
|
||||||
|
return len(me.mi.Pieces) / pieceHash.Size()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMetaDataFromMetaInfo(mi *metainfo.MetaInfo) MetaData {
|
||||||
|
return metaInfoMetaData{mi}
|
||||||
|
}
|
||||||
|
|
||||||
|
type MetaData interface {
|
||||||
|
PieceHashes() []string
|
||||||
|
Files() []metainfo.FileInfo
|
||||||
|
Name() string
|
||||||
|
PieceLength() int64
|
||||||
|
PieceCount() int
|
||||||
|
}
|
||||||
|
|
||||||
|
func mmapTorrentData(md MetaData, location string) (mms mmap_span.MMapSpan, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mms.Close()
|
mms.Close()
|
||||||
mms = nil
|
mms = nil
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
for _, miFile := range metaInfo.Files {
|
for _, miFile := range md.Files() {
|
||||||
fileName := filepath.Join(append([]string{location, metaInfo.Name}, miFile.Path...)...)
|
fileName := filepath.Join(append([]string{location, md.Name()}, miFile.Path...)...)
|
||||||
err = os.MkdirAll(filepath.Dir(fileName), 0777)
|
err = os.MkdirAll(filepath.Dir(fileName), 0777)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
@ -150,3 +176,11 @@ func mmapTorrentData(metaInfo *metainfo.MetaInfo, location string) (mms mmap_spa
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func metadataPieceSize(totalSize int, piece int) int {
|
||||||
|
ret := totalSize - piece*(1<<14)
|
||||||
|
if ret > 1<<14 {
|
||||||
|
ret = 1 << 14
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
|
@ -33,6 +33,9 @@ const (
|
||||||
Request // 6
|
Request // 6
|
||||||
Piece // 7
|
Piece // 7
|
||||||
Cancel // 8
|
Cancel // 8
|
||||||
|
Extended = 20
|
||||||
|
|
||||||
|
HandshakeExtendedID = 0
|
||||||
)
|
)
|
||||||
|
|
||||||
type Message struct {
|
type Message struct {
|
||||||
|
@ -41,6 +44,8 @@ type Message struct {
|
||||||
Index, Begin, Length Integer
|
Index, Begin, Length Integer
|
||||||
Piece []byte
|
Piece []byte
|
||||||
Bitfield []bool
|
Bitfield []bool
|
||||||
|
ExtendedID byte
|
||||||
|
ExtendedPayload []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msg Message) MarshalBinary() (data []byte, err error) {
|
func (msg Message) MarshalBinary() (data []byte, err error) {
|
||||||
|
@ -77,8 +82,14 @@ func (msg Message) MarshalBinary() (data []byte, err error) {
|
||||||
if n != len(msg.Piece) {
|
if n != len(msg.Piece) {
|
||||||
panic(n)
|
panic(n)
|
||||||
}
|
}
|
||||||
|
case Extended:
|
||||||
|
err = buf.WriteByte(msg.ExtendedID)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = buf.Write(msg.ExtendedPayload)
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("unknown message type: %s", msg.Type)
|
err = fmt.Errorf("unknown message type: %v", msg.Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
data = make([]byte, 4+buf.Len())
|
data = make([]byte, 4+buf.Len())
|
||||||
|
@ -159,6 +170,12 @@ func (d *Decoder) Decode(msg *Message) (err error) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
msg.Piece, err = ioutil.ReadAll(r)
|
msg.Piece, err = ioutil.ReadAll(r)
|
||||||
|
case Extended:
|
||||||
|
msg.ExtendedID, err = r.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
msg.ExtendedPayload, err = ioutil.ReadAll(r)
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("unknown message type %#v", c)
|
err = fmt.Errorf("unknown message type %#v", c)
|
||||||
}
|
}
|
||||||
|
|
70
torrent.go
70
torrent.go
|
@ -4,13 +4,13 @@ import (
|
||||||
"container/list"
|
"container/list"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"bitbucket.org/anacrolix/go.torrent/mmap_span"
|
"bitbucket.org/anacrolix/go.torrent/mmap_span"
|
||||||
pp "bitbucket.org/anacrolix/go.torrent/peer_protocol"
|
pp "bitbucket.org/anacrolix/go.torrent/peer_protocol"
|
||||||
"bitbucket.org/anacrolix/go.torrent/tracker"
|
"bitbucket.org/anacrolix/go.torrent/tracker"
|
||||||
metainfo "github.com/nsf/libtorgo/torrent"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (t *torrent) PieceNumPendingBytes(index pp.Integer) (count pp.Integer) {
|
func (t *torrent) PieceNumPendingBytes(index pp.Integer) (count pp.Integer) {
|
||||||
|
@ -29,7 +29,7 @@ type torrent struct {
|
||||||
InfoHash InfoHash
|
InfoHash InfoHash
|
||||||
Pieces []*piece
|
Pieces []*piece
|
||||||
Data mmap_span.MMapSpan
|
Data mmap_span.MMapSpan
|
||||||
MetaInfo *metainfo.MetaInfo
|
Info MetaData
|
||||||
Conns []*connection
|
Conns []*connection
|
||||||
Peers []Peer
|
Peers []Peer
|
||||||
Priorities *list.List
|
Priorities *list.List
|
||||||
|
@ -37,6 +37,39 @@ type torrent struct {
|
||||||
// mirror their respective URLs from the announce-list key.
|
// mirror their respective URLs from the announce-list key.
|
||||||
Trackers [][]tracker.Client
|
Trackers [][]tracker.Client
|
||||||
lastReadPiece int
|
lastReadPiece int
|
||||||
|
DisplayName string
|
||||||
|
MetaData []byte
|
||||||
|
MetaDataHave []bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *torrent) GotAllMetadataPieces() bool {
|
||||||
|
if t.MetaDataHave == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, have := range t.MetaDataHave {
|
||||||
|
if !have {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *torrent) SetMetaDataSize(bytes int64) {
|
||||||
|
if t.MetaData != nil {
|
||||||
|
if len(t.MetaData) != int(bytes) {
|
||||||
|
log.Printf("new metadata_size differs")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.MetaData = make([]byte, bytes)
|
||||||
|
t.MetaDataHave = make([]bool, (bytes+(1<<14)-1)/(1<<14))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *torrent) Name() string {
|
||||||
|
if t.Info == nil {
|
||||||
|
return t.DisplayName
|
||||||
|
}
|
||||||
|
return t.Info.Name()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *torrent) pieceStatusChar(index int) byte {
|
func (t *torrent) pieceStatusChar(index int) byte {
|
||||||
|
@ -71,10 +104,17 @@ func (t *torrent) WriteStatus(w io.Writer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *torrent) String() string {
|
func (t *torrent) String() string {
|
||||||
return t.MetaInfo.Name
|
return t.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *torrent) haveInfo() bool {
|
||||||
|
return t.Info != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *torrent) BytesLeft() (left int64) {
|
func (t *torrent) BytesLeft() (left int64) {
|
||||||
|
if !t.haveInfo() {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
for i := pp.Integer(0); i < pp.Integer(t.NumPieces()); i++ {
|
for i := pp.Integer(0); i < pp.Integer(t.NumPieces()); i++ {
|
||||||
left += int64(t.PieceNumPendingBytes(i))
|
left += int64(t.PieceNumPendingBytes(i))
|
||||||
}
|
}
|
||||||
|
@ -96,7 +136,7 @@ func (t *torrent) ChunkCount() (num int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *torrent) UsualPieceSize() int {
|
func (t *torrent) UsualPieceSize() int {
|
||||||
return int(t.MetaInfo.PieceLength)
|
return int(t.Info.PieceLength())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *torrent) LastPieceSize() int {
|
func (t *torrent) LastPieceSize() int {
|
||||||
|
@ -104,7 +144,7 @@ func (t *torrent) LastPieceSize() int {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *torrent) NumPieces() int {
|
func (t *torrent) NumPieces() int {
|
||||||
return len(t.MetaInfo.Pieces) / pieceHash.Size()
|
return t.Info.PieceCount()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *torrent) NumPiecesCompleted() (num int) {
|
func (t *torrent) NumPiecesCompleted() (num int) {
|
||||||
|
@ -168,16 +208,16 @@ func torrentRequestOffset(torrentLength, pieceSize int64, r request) (off int64)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *torrent) requestOffset(r request) int64 {
|
func (t *torrent) requestOffset(r request) int64 {
|
||||||
return torrentRequestOffset(t.Length(), t.MetaInfo.PieceLength, r)
|
return torrentRequestOffset(t.Length(), t.Info.PieceLength(), r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the request that would include the given offset into the torrent data.
|
// Return the request that would include the given offset into the torrent data.
|
||||||
func (t *torrent) offsetRequest(off int64) (req request, ok bool) {
|
func (t *torrent) offsetRequest(off int64) (req request, ok bool) {
|
||||||
return torrentOffsetRequest(t.Length(), t.MetaInfo.PieceLength, chunkSize, off)
|
return torrentOffsetRequest(t.Length(), t.Info.PieceLength(), chunkSize, off)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *torrent) WriteChunk(piece int, begin int64, data []byte) (err error) {
|
func (t *torrent) WriteChunk(piece int, begin int64, data []byte) (err error) {
|
||||||
_, err = t.Data.WriteAt(data, int64(piece)*t.MetaInfo.PieceLength+begin)
|
_, err = t.Data.WriteAt(data, int64(piece)*t.Info.PieceLength()+begin)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,7 +233,7 @@ func (t *torrent) pendAllChunkSpecs(index pp.Integer) {
|
||||||
if piece.PendingChunkSpecs == nil {
|
if piece.PendingChunkSpecs == nil {
|
||||||
piece.PendingChunkSpecs = make(
|
piece.PendingChunkSpecs = make(
|
||||||
map[chunkSpec]struct{},
|
map[chunkSpec]struct{},
|
||||||
(t.MetaInfo.PieceLength+chunkSize-1)/chunkSize)
|
(t.Info.PieceLength()+chunkSize-1)/chunkSize)
|
||||||
}
|
}
|
||||||
c := chunkSpec{
|
c := chunkSpec{
|
||||||
Begin: 0,
|
Begin: 0,
|
||||||
|
@ -218,17 +258,17 @@ type Peer struct {
|
||||||
|
|
||||||
func (t *torrent) PieceLength(piece pp.Integer) (len_ pp.Integer) {
|
func (t *torrent) PieceLength(piece pp.Integer) (len_ pp.Integer) {
|
||||||
if int(piece) == t.NumPieces()-1 {
|
if int(piece) == t.NumPieces()-1 {
|
||||||
len_ = pp.Integer(t.Data.Size() % t.MetaInfo.PieceLength)
|
len_ = pp.Integer(t.Data.Size() % t.Info.PieceLength())
|
||||||
}
|
}
|
||||||
if len_ == 0 {
|
if len_ == 0 {
|
||||||
len_ = pp.Integer(t.MetaInfo.PieceLength)
|
len_ = pp.Integer(t.Info.PieceLength())
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *torrent) HashPiece(piece pp.Integer) (ps pieceSum) {
|
func (t *torrent) HashPiece(piece pp.Integer) (ps pieceSum) {
|
||||||
hash := pieceHash.New()
|
hash := pieceHash.New()
|
||||||
n, err := t.Data.WriteSectionTo(hash, int64(piece)*t.MetaInfo.PieceLength, t.MetaInfo.PieceLength)
|
n, err := t.Data.WriteSectionTo(hash, int64(piece)*t.Info.PieceLength(), t.Info.PieceLength())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -239,6 +279,9 @@ func (t *torrent) HashPiece(piece pp.Integer) (ps pieceSum) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
func (t *torrent) haveAllPieces() bool {
|
func (t *torrent) haveAllPieces() bool {
|
||||||
|
if t.Info == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
for _, piece := range t.Pieces {
|
for _, piece := range t.Pieces {
|
||||||
if !piece.Complete() {
|
if !piece.Complete() {
|
||||||
return false
|
return false
|
||||||
|
@ -257,6 +300,9 @@ func (me *torrent) haveAnyPieces() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *torrent) wantPiece(index int) bool {
|
func (t *torrent) wantPiece(index int) bool {
|
||||||
|
if !t.haveInfo() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
p := t.Pieces[index]
|
p := t.Pieces[index]
|
||||||
return p.EverHashed && len(p.PendingChunkSpecs) != 0
|
return p.EverHashed && len(p.PendingChunkSpecs) != 0
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue