diff --git a/client.go b/client.go index 48889843..13589a0d 100644 --- a/client.go +++ b/client.go @@ -19,8 +19,10 @@ import ( "bufio" "container/list" "crypto/rand" + "crypto/sha1" "errors" "fmt" + "github.com/nsf/libtorgo/bencode" "io" "log" mathRand "math/rand" @@ -57,6 +59,9 @@ func (me *Client) PrioritizeDataRegion(ih InfoHash, off, len_ int64) error { if t == nil { return errors.New("no such active torrent") } + if t.Info == nil { + return errors.New("missing metadata") + } newPriorities := make([]request, 0, (len_+chunkSize-1)/chunkSize) for len_ > 0 { req, ok := t.offsetRequest(off) @@ -113,7 +118,7 @@ func (cl *Client) WriteStatus(w io.Writer) { cl.mu.Lock() defer cl.mu.Unlock() 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) } } @@ -128,7 +133,7 @@ func (cl *Client) TorrentReadAt(ih InfoHash, off int64, p []byte) (n int, err er err = errors.New("unknown torrent") 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. if index < 0 { err = os.ErrInvalid @@ -287,7 +292,7 @@ func (me *Client) runConnection(sock net.Conn, torrent *torrent) (err error) { PeerChoked: true, write: make(chan []byte), post: make(chan pp.Message), - PeerMaxRequests: 250, + PeerMaxRequests: 64, } defer func() { // 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.writeOptimizer() 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 { conn.write <- pp.Bytes(torrent.InfoHash[:]) conn.write <- pp.Bytes(me.PeerId[:]) @@ -344,6 +349,23 @@ func (me *Client) runConnection(sock net.Conn, torrent *torrent) (err error) { return } 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() { conn.Post(pp.Message{ Type: pp.Bitfield, @@ -358,13 +380,13 @@ func (me *Client) runConnection(sock net.Conn, torrent *torrent) (err error) { return } -func (me *Client) peerGotPiece(torrent *torrent, conn *connection, piece int) { - if conn.PeerPieces == nil { - conn.PeerPieces = make([]bool, len(torrent.Pieces)) +func (me *Client) peerGotPiece(t *torrent, c *connection, piece int) { + for piece >= len(c.PeerPieces) { + c.PeerPieces = append(c.PeerPieces, false) } - conn.PeerPieces[piece] = true - if torrent.wantPiece(piece) { - me.replenishConnRequests(torrent, conn) + c.PeerPieces[piece] = true + if t.wantPiece(piece) { + me.replenishConnRequests(t, c) } } @@ -388,6 +410,31 @@ func (cl *Client) connDeleteRequest(t *torrent, cn *connection, r request) { 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 { decoder := pp.Decoder{ 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) } case pp.Bitfield: - if len(msg.Bitfield) < t.NumPieces() { - err = errors.New("received invalid bitfield") - break - } if c.PeerPieces != nil { err = errors.New("received unexpected bitfield") 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 { if has { me.peerGotPiece(t, c, index) @@ -471,6 +521,79 @@ func (me *Client) connectionLoop(t *torrent, c *connection) error { } case pp.Piece: 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: 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 } -// 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(metaInfo *metainfo.MetaInfo, dataDir string) (t *torrent, err error) { - t = &torrent{ - InfoHash: BytesInfoHash(metaInfo.InfoHash), - MetaInfo: metaInfo, - } - t.Data, err = mmapTorrentData(metaInfo, dataDir) +func (cl *Client) setMetaData(t *torrent, md MetaData) (err error) { + t.Data, err = mmapTorrentData(md, cl.DataDir) if err != nil { return } - for offset := 0; offset < len(metaInfo.Pieces); offset += pieceHash.Size() { - hash := metaInfo.Pieces[offset : offset+pieceHash.Size()] - if len(hash) != pieceHash.Size() { - err = errors.New("bad piece hash in metainfo") - return - } + for _, hash := range md.PieceHashes() { piece := &piece{} - copyHashSum(piece.Hash[:], hash) + copyHashSum(piece.Hash[:], []byte(hash)) t.Pieces = append(t.Pieces, piece) t.pendAllChunkSpecs(pp.Integer(len(t.Pieces) - 1)) } - t.Trackers = make([][]tracker.Client, len(metaInfo.AnnounceList)) - for tierIndex := range metaInfo.AnnounceList { + t.Priorities = list.New() + + // 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] - for _, url := range metaInfo.AnnounceList[tierIndex] { + for _, url := range announceList[tierIndex] { tr, err := tracker.New(url) if err != nil { log.Print(err) @@ -585,36 +720,52 @@ func newTorrent(metaInfo *metainfo.MetaInfo, dataDir string) (t *torrent, err er return } -// Adds the torrent to the client. -func (me *Client) AddTorrent(metaInfo *metainfo.MetaInfo) error { - torrent, err := newTorrent(metaInfo, me.DataDir) +func (cl *Client) AddMagnet(uri string) (err error) { + m, err := ParseMagnetURI(uri) if err != nil { - return err + return } - me.mu.Lock() - defer me.mu.Unlock() - if _, ok := me.torrents[torrent.InfoHash]; ok { - return torrent.Close() + t, err := newTorrent(m.InfoHash, [][]string{m.Trackers}) + if err != nil { + return } - me.torrents[torrent.InfoHash] = torrent - me.DownloadStrategy.TorrentStarted(torrent) + t.DisplayName = m.DisplayName + 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 { - go me.announceTorrent(torrent) + go me.announceTorrent(t) } - torrent.Priorities = list.New() + return +} - // Queue all pieces for hashing. This is done sequentially to avoid - // spamming goroutines. - for _, p := range torrent.Pieces { - p.QueuedForHash = true +// Adds the torrent to the client. +func (me *Client) AddTorrent(metaInfo *metainfo.MetaInfo) (err error) { + t, err := newTorrent(BytesInfoHash(metaInfo.InfoHash), metaInfo.AnnounceList) + if err != nil { + return } - go func() { - for i := range torrent.Pieces { - me.verifyPiece(torrent, pp.Integer(i)) - } - }() - - return nil + err = me.addTorrent(t) + if err != nil { + return + } + err = me.setMetaData(t, metaInfoMetaData{metaInfo}) + if err != nil { + return + } + return } 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? if _, ok := t.Pieces[req.Index].PendingChunkSpecs[req.chunkSpec]; !ok { + log.Printf("got unnecessary chunk from %v: %q", req, string(c.PeerId[:])) return nil } diff --git a/cmd/dht-server/main.go b/cmd/dht-server/main.go index b18dfc46..667a8c8a 100644 --- a/cmd/dht-server/main.go +++ b/cmd/dht-server/main.go @@ -81,7 +81,7 @@ func init() { } func saveTable() error { - goodNodes := s.GoodNodes() + goodNodes := s.Nodes() if *tableFileName == "" { if len(goodNodes) != 0 { log.Printf("discarding %d good nodes!", len(goodNodes)) @@ -123,6 +123,8 @@ func main() { if err != nil { log.Printf("error bootstrapping: %s", err) s.StopServing() + } else { + log.Print("bootstrapping complete") } }() err := s.Serve() diff --git a/cmd/torrent/main.go b/cmd/torrent/main.go index 59c1d801..6874ef67 100644 --- a/cmd/torrent/main.go +++ b/cmd/torrent/main.go @@ -8,6 +8,7 @@ import ( "net/http" _ "net/http/pprof" "os" + "strings" metainfo "github.com/nsf/libtorgo/torrent" @@ -53,16 +54,30 @@ func main() { return } for _, arg := range flag.Args() { - metaInfo, err := metainfo.LoadFromFile(arg) - if err != nil { - log.Fatal(err) + var ih torrent.InfoHash + if strings.HasPrefix(arg, "magnet:") { + 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) - if err != nil { - log.Fatal(err) - } - client.PrioritizeDataRegion(torrent.BytesInfoHash(metaInfo.InfoHash), 0, 999999999) - err = client.AddPeers(torrent.BytesInfoHash(metaInfo.InfoHash), func() []torrent.Peer { + client.PrioritizeDataRegion(ih, 0, 999999999) + err := client.AddPeers(ih, func() []torrent.Peer { if *testPeer == "" { return nil } diff --git a/connection.go b/connection.go index c56329f6..7a116b2e 100644 --- a/connection.go +++ b/connection.go @@ -27,13 +27,14 @@ type connection struct { Requests map[request]struct{} // Stuff controlled by the remote peer. - PeerId [20]byte - PeerInterested bool - PeerChoked bool - PeerRequests map[request]struct{} - PeerExtensions [8]byte - PeerPieces []bool - PeerMaxRequests int // Maximum pending requests the peer allows. + PeerId [20]byte + PeerInterested bool + PeerChoked bool + PeerRequests map[request]struct{} + PeerExtensions [8]byte + PeerPieces []bool + PeerMaxRequests int // Maximum pending requests the peer allows. + PeerExtensionIDs map[string]int64 } func (cn *connection) completedString() string { diff --git a/dht/dht.go b/dht/dht.go index 717c45ad..0bc6c4f4 100644 --- a/dht/dht.go +++ b/dht/dht.go @@ -56,7 +56,18 @@ type transaction struct { 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 == "" { var id [20]byte h := crypto.SHA1.New() @@ -74,10 +85,12 @@ func (s *Server) setDefaults() { } s.ID = string(id[:]) } + return } -func (s *Server) Init() { - s.setDefaults() +func (s *Server) Init() error { + return s.setDefaults() + //s.nodes = make(map[string]*Node) } func (s *Server) Serve() error { @@ -401,13 +414,13 @@ func (s *Server) Bootstrap() (err error) { return } -func (s *Server) GoodNodes() (nis []NodeInfo) { +func (s *Server) Nodes() (nis []NodeInfo) { s.mu.Lock() defer s.mu.Unlock() for _, node := range s.nodes { - if !node.Good() { - continue - } + // if !node.Good() { + // continue + // } ni := NodeInfo{ Addr: node.addr, } diff --git a/magnet.go b/magnet.go new file mode 100644 index 00000000..4a842c71 --- /dev/null +++ b/magnet.go @@ -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 +} diff --git a/misc.go b/misc.go index c53976fa..3ffb2852 100644 --- a/misc.go +++ b/misc.go @@ -4,13 +4,13 @@ import ( "bitbucket.org/anacrolix/go.torrent/mmap_span" "crypto" "errors" + metainfo "github.com/nsf/libtorgo/torrent" "math/rand" "os" "path/filepath" "time" "bitbucket.org/anacrolix/go.torrent/peer_protocol" - metainfo "github.com/nsf/libtorgo/torrent" "launchpad.net/gommap" ) @@ -103,15 +103,41 @@ var ( 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() { if err != nil { mms.Close() mms = nil } }() - for _, miFile := range metaInfo.Files { - fileName := filepath.Join(append([]string{location, metaInfo.Name}, miFile.Path...)...) + for _, miFile := range md.Files() { + fileName := filepath.Join(append([]string{location, md.Name()}, miFile.Path...)...) err = os.MkdirAll(filepath.Dir(fileName), 0777) if err != nil { return @@ -150,3 +176,11 @@ func mmapTorrentData(metaInfo *metainfo.MetaInfo, location string) (mms mmap_spa } return } + +func metadataPieceSize(totalSize int, piece int) int { + ret := totalSize - piece*(1<<14) + if ret > 1<<14 { + ret = 1 << 14 + } + return ret +} diff --git a/peer_protocol/protocol.go b/peer_protocol/protocol.go index 90b01359..9900a033 100644 --- a/peer_protocol/protocol.go +++ b/peer_protocol/protocol.go @@ -33,6 +33,9 @@ const ( Request // 6 Piece // 7 Cancel // 8 + Extended = 20 + + HandshakeExtendedID = 0 ) type Message struct { @@ -41,6 +44,8 @@ type Message struct { Index, Begin, Length Integer Piece []byte Bitfield []bool + ExtendedID byte + ExtendedPayload []byte } 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) { panic(n) } + case Extended: + err = buf.WriteByte(msg.ExtendedID) + if err != nil { + return + } + _, err = buf.Write(msg.ExtendedPayload) 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()) @@ -159,6 +170,12 @@ func (d *Decoder) Decode(msg *Message) (err error) { break } msg.Piece, err = ioutil.ReadAll(r) + case Extended: + msg.ExtendedID, err = r.ReadByte() + if err != nil { + break + } + msg.ExtendedPayload, err = ioutil.ReadAll(r) default: err = fmt.Errorf("unknown message type %#v", c) } diff --git a/torrent.go b/torrent.go index 1e3cf91b..e051d8b9 100644 --- a/torrent.go +++ b/torrent.go @@ -4,13 +4,13 @@ import ( "container/list" "fmt" "io" + "log" "net" "sort" "bitbucket.org/anacrolix/go.torrent/mmap_span" pp "bitbucket.org/anacrolix/go.torrent/peer_protocol" "bitbucket.org/anacrolix/go.torrent/tracker" - metainfo "github.com/nsf/libtorgo/torrent" ) func (t *torrent) PieceNumPendingBytes(index pp.Integer) (count pp.Integer) { @@ -29,7 +29,7 @@ type torrent struct { InfoHash InfoHash Pieces []*piece Data mmap_span.MMapSpan - MetaInfo *metainfo.MetaInfo + Info MetaData Conns []*connection Peers []Peer Priorities *list.List @@ -37,6 +37,39 @@ type torrent struct { // mirror their respective URLs from the announce-list key. Trackers [][]tracker.Client 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 { @@ -71,10 +104,17 @@ func (t *torrent) WriteStatus(w io.Writer) { } 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) { + if !t.haveInfo() { + return -1 + } for i := pp.Integer(0); i < pp.Integer(t.NumPieces()); i++ { left += int64(t.PieceNumPendingBytes(i)) } @@ -96,7 +136,7 @@ func (t *torrent) ChunkCount() (num int) { } func (t *torrent) UsualPieceSize() int { - return int(t.MetaInfo.PieceLength) + return int(t.Info.PieceLength()) } func (t *torrent) LastPieceSize() int { @@ -104,7 +144,7 @@ func (t *torrent) LastPieceSize() int { } func (t *torrent) NumPieces() int { - return len(t.MetaInfo.Pieces) / pieceHash.Size() + return t.Info.PieceCount() } 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 { - 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. 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) { - _, err = t.Data.WriteAt(data, int64(piece)*t.MetaInfo.PieceLength+begin) + _, err = t.Data.WriteAt(data, int64(piece)*t.Info.PieceLength()+begin) return } @@ -193,7 +233,7 @@ func (t *torrent) pendAllChunkSpecs(index pp.Integer) { if piece.PendingChunkSpecs == nil { piece.PendingChunkSpecs = make( map[chunkSpec]struct{}, - (t.MetaInfo.PieceLength+chunkSize-1)/chunkSize) + (t.Info.PieceLength()+chunkSize-1)/chunkSize) } c := chunkSpec{ Begin: 0, @@ -218,17 +258,17 @@ type Peer struct { func (t *torrent) PieceLength(piece pp.Integer) (len_ pp.Integer) { 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 { - len_ = pp.Integer(t.MetaInfo.PieceLength) + len_ = pp.Integer(t.Info.PieceLength()) } return } func (t *torrent) HashPiece(piece pp.Integer) (ps pieceSum) { 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 { panic(err) } @@ -239,6 +279,9 @@ func (t *torrent) HashPiece(piece pp.Integer) (ps pieceSum) { return } func (t *torrent) haveAllPieces() bool { + if t.Info == nil { + return false + } for _, piece := range t.Pieces { if !piece.Complete() { return false @@ -257,6 +300,9 @@ func (me *torrent) haveAnyPieces() bool { } func (t *torrent) wantPiece(index int) bool { + if !t.haveInfo() { + return false + } p := t.Pieces[index] return p.EverHashed && len(p.PendingChunkSpecs) != 0 }