client.go | 133 ++++++++++++++++++++++++++++++++++------------------- client_test.go | 6 +++++- cmd/torrent/main.go | 2 +- cmd/torrentfs/main.go | 2 +- connection.go | 7 ++++--- fs/torrentfs.go | 53 ++++++++++++++++++++++++++--------------------------- fs/torrentfs_test.go | 15 ++++++++++++--- misc.go | 43 ++++++++++--------------------------------- peer_protocol/protocol.go | 4 ++++ testutil/testutil.go | 5 ++--- torrent.go | 132 +++++++++++++++++++++++++++++++++++++++++++---------- diff --git a/client.go b/client.go index c159d7426c2ccf96cc912bb37108b69c8a330d34..cecc513bb6fbcdd795724a448fd87a6ad97d4377 100644 --- a/client.go +++ b/client.go @@ -22,7 +22,6 @@ "crypto/rand" "crypto/sha1" "errors" "fmt" - "github.com/nsf/libtorgo/bencode" "io" "log" mathRand "math/rand" @@ -32,7 +31,8 @@ "sync" "syscall" "time" - metainfo "github.com/nsf/libtorgo/torrent" + "github.com/anacrolix/libtorgo/metainfo" + "github.com/nsf/libtorgo/bencode" pp "bitbucket.org/anacrolix/go.torrent/peer_protocol" "bitbucket.org/anacrolix/go.torrent/tracker" @@ -59,7 +59,7 @@ t := me.torrent(ih) if t == nil { return errors.New("no such active torrent") } - if t.Info == nil { + if !t.haveInfo() { return errors.New("missing metadata") } newPriorities := make([]request, 0, (len_+chunkSize-1)/chunkSize) @@ -133,7 +133,7 @@ if t == nil { err = errors.New("unknown torrent") return } - index := pp.Integer(off / t.Info.PieceLength()) + index := pp.Integer(off / int64(t.UsualPieceSize())) // Reading outside the bounds of a file is an error. if index < 0 { err = os.ErrInvalid @@ -354,11 +354,15 @@ conn.Post(pp.Message{ Type: pp.Extended, ExtendedID: pp.HandshakeExtendedID, ExtendedPayload: func() []byte { - b, err := bencode.Marshal(map[string]interface{}{ + d := map[string]interface{}{ "m": map[string]int{ "ut_metadata": 1, }, - }) + } + if torrent.metadataSizeKnown() { + d["metadata_size"] = torrent.metadataSize() + } + b, err := bencode.Marshal(d) if err != nil { panic(err) } @@ -415,8 +419,8 @@ if t.haveInfo() { return } var pending []int - for index, have := range t.MetaDataHave { - if !have { + for index := 0; index < t.MetadataPieceCount(); index++ { + if !t.HaveMetadataPiece(index) { pending = append(pending, index) } } @@ -438,6 +442,63 @@ }) } } +func (cl *Client) completedMetadata(t *torrent) { + h := sha1.New() + h.Write(t.MetaData) + var ih InfoHash + copy(ih[:], h.Sum(nil)[:]) + if ih != t.InfoHash { + log.Print("bad metadata") + t.InvalidateMetadata() + return + } + var info metainfo.Info + err := bencode.Unmarshal(t.MetaData, &info) + if err != nil { + log.Printf("error unmarshalling metadata: %s", err) + t.InvalidateMetadata() + return + } + cl.setMetaData(t, info, t.MetaData) +} + +func (cl *Client) gotMetadataExtensionMsg(payload []byte, t *torrent, c *connection) (err error) { + var d map[string]int + err = bencode.Unmarshal(payload, &d) + if err != nil { + err = fmt.Errorf("error unmarshalling payload: %s", err) + return + } + msgType, ok := d["msg_type"] + if !ok { + err = errors.New("missing msg_type field") + return + } + piece := d["piece"] + log.Println(piece, d["total_size"], len(payload)) + switch msgType { + case pp.DataMetadataExtensionMsgType: + if t.haveInfo() { + break + } + t.SaveMetadataPiece(piece, payload[len(payload)-metadataPieceSize(d["total_size"], piece):]) + if !t.HaveAllMetadataPieces() { + break + } + cl.completedMetadata(t) + case pp.RequestMetadataExtensionMsgType: + if !t.HaveMetadataPiece(piece) { + c.Post(t.NewMetadataExtensionMessage(c, pp.RejectMetadataExtensionMsgType, d["piece"], nil)) + break + } + c.Post(t.NewMetadataExtensionMessage(c, pp.DataMetadataExtensionMsgType, piece, t.MetaData[(1<<14)*piece:(1<<14)*piece+t.metadataPieceSize(piece)])) + case pp.RejectMetadataExtensionMsgType: + default: + err = errors.New("unknown msg_type value") + } + return +} + func (me *Client) connectionLoop(t *torrent, c *connection) error { decoder := pp.Decoder{ R: bufio.NewReader(c.Socket), @@ -565,37 +626,17 @@ if !ok { log.Printf("bad metadata_size type: %T", metadata_sizeUntyped) } else { log.Printf("metadata_size: %d", metadata_size) - t.SetMetaDataSize(metadata_size) + t.SetMetadataSize(metadata_size) } } + log.Println(metadata_sizeUntyped, c.PeerExtensionIDs) 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) - } + err = me.gotMetadataExtensionMsg(msg.ExtendedPayload, t, c) + default: + err = fmt.Errorf("unexpected extended message ID: %s", msg.ExtendedID) } default: err = fmt.Errorf("received unknown message type: %#v", msg.Type) @@ -665,20 +706,11 @@ me.mu.Unlock() return nil } -func (cl *Client) setMetaData(t *torrent, md MetaData) (err error) { - t.Info = md - t.Data, err = mmapTorrentData(md, cl.DataDir) +func (cl *Client) setMetaData(t *torrent, md metainfo.Info, bytes []byte) (err error) { + err = t.setMetadata(md, cl.DataDir, bytes) if err != nil { return } - for _, hash := range md.PieceHashes() { - piece := &piece{} - copyHashSum(piece.Hash[:], []byte(hash)) - t.Pieces = append(t.Pieces, piece) - t.pendAllChunkSpecs(pp.Integer(len(t.Pieces) - 1)) - } - t.Priorities = list.New() - // Queue all pieces for hashing. This is done sequentially to avoid // spamming goroutines. for _, p := range t.Pieces { @@ -767,7 +799,7 @@ err = me.addTorrent(t) if err != nil { return } - err = me.setMetaData(t, metaInfoMetaData{metaInfo}) + err = me.setMetaData(t, metaInfo.Info, metaInfo.InfoBytes) if err != nil { return } @@ -921,7 +953,7 @@ } } ppbs := t.piecesByPendingBytes() // Then finish off incomplete pieces in order of bytes remaining. - for _, heatThreshold := range []int{0, 4, 100} { + for _, heatThreshold := range []int{0, 1, 4, 100} { for _, pieceIndex := range ppbs { for _, chunkSpec := range t.Pieces[pieceIndex].shuffledPendingChunkSpecs() { r := request{pieceIndex, chunkSpec} @@ -973,6 +1005,10 @@ func (ResponsiveDownloadStrategy) DeleteRequest(*torrent, request) {} func (me *ResponsiveDownloadStrategy) FillRequests(t *torrent, c *connection) { for e := t.Priorities.Front(); e != nil; e = e.Next() { + req := e.Value.(request) + if _, ok := t.Pieces[req.Index].PendingChunkSpecs[req.chunkSpec]; !ok { + panic(req) + } if !c.Request(e.Value.(request)) { return } @@ -1004,13 +1040,14 @@ func (me *Client) replenishConnRequests(t *torrent, c *connection) { me.DownloadStrategy.FillRequests(t, c) me.assertRequestHeat() - if len(c.Requests) == 0 { + if len(c.Requests) == 0 && !c.PeerChoked { c.SetInterested(false) } } func (me *Client) downloadedChunk(t *torrent, c *connection, msg *pp.Message) error { req := newRequest(msg.Index, msg.Begin, pp.Integer(len(msg.Piece))) + log.Println("got", req) // Request has been satisfied. me.connDeleteRequest(t, c, req) @@ -1028,7 +1065,6 @@ err := t.WriteChunk(int(msg.Index), int64(msg.Begin), msg.Piece) if err != nil { return err } - me.dataReady(dataSpec{t.InfoHash, req}) // Record that we have the chunk. delete(t.Pieces[req.Index].PendingChunkSpecs, req.chunkSpec) @@ -1053,6 +1089,7 @@ me.replenishConnRequests(t, c) } } + me.dataReady(dataSpec{t.InfoHash, req}) return nil } diff --git a/client_test.go b/client_test.go index 89e94b10f992ccc4bfbe6d2961a77ef321c329d7..04863fb6cc1158c966fc07c7ecac5e6c32e0e33c 100644 --- a/client_test.go +++ b/client_test.go @@ -29,7 +29,11 @@ func TestTorrentInitialState(t *testing.T) { dir, mi := testutil.GreetingTestTorrent() defer os.RemoveAll(dir) - tor, err := newTorrent(mi, dir) + tor, err := newTorrent(BytesInfoHash(mi.InfoHash), nil) + if err != nil { + t.Fatal(err) + } + err = tor.setMetadata(mi.Info, dir, mi.InfoBytes) if err != nil { t.Fatal(err) } diff --git a/cmd/torrent/main.go b/cmd/torrent/main.go index 5b7bc54e45616bc1f0c535da4e4a47a811c4ab4e..62b5472e782bbb92bdb6a82e46b129da6e7ef791 100644 --- a/cmd/torrent/main.go +++ b/cmd/torrent/main.go @@ -10,7 +10,7 @@ _ "net/http/pprof" "os" "strings" - metainfo "github.com/nsf/libtorgo/torrent" + "github.com/anacrolix/libtorgo/metainfo" "bitbucket.org/anacrolix/go.torrent" ) diff --git a/cmd/torrentfs/main.go b/cmd/torrentfs/main.go index 97f916b315d464a7b0de829d2493e1486d932239..21444052a9dc08f7c1853987011857ba887c3310 100644 --- a/cmd/torrentfs/main.go +++ b/cmd/torrentfs/main.go @@ -17,7 +17,7 @@ "bazil.org/fuse" fusefs "bazil.org/fuse/fs" "bitbucket.org/anacrolix/go.torrent" "bitbucket.org/anacrolix/go.torrent/fs" - metainfo "github.com/nsf/libtorgo/torrent" + "github.com/anacrolix/libtorgo/metainfo" ) var ( diff --git a/connection.go b/connection.go index 7a116b2e62e153d7777d4962199713b854b880a7..ddd875d416513f0fa6d2c542b98064a02a7f56bf 100644 --- a/connection.go +++ b/connection.go @@ -119,12 +119,12 @@ } if !c.PeerHasPiece(chunk.Index) { return true } + if c.RequestPending(chunk) { + return true + } c.SetInterested(true) if c.PeerChoked { return false - } - if c.RequestPending(chunk) { - return true } if c.Requests == nil { c.Requests = make(map[request]struct{}, c.PeerMaxRequests) @@ -214,6 +214,7 @@ // Writes buffers to the socket from the write channel. func (conn *connection) writer() { for b := range conn.write { _, err := conn.Socket.Write(b) + // log.Printf("wrote %q to %s", b, conn.Socket.RemoteAddr()) if err != nil { if !conn.getClosed() { log.Print(err) diff --git a/fs/torrentfs.go b/fs/torrentfs.go index acda26b214bb09c4f2da43d491d4d9530701ad9d..726b8fe58c188a19b9b0b814e6e418cd798069d0 100644 --- a/fs/torrentfs.go +++ b/fs/torrentfs.go @@ -1,14 +1,13 @@ package torrentfs import ( - "log" - "os" - "sync" - "bazil.org/fuse" fusefs "bazil.org/fuse/fs" "bitbucket.org/anacrolix/go.torrent" - metainfo "github.com/nsf/libtorgo/torrent" + "github.com/anacrolix/libtorgo/metainfo" + "log" + "os" + "sync" ) const ( @@ -31,7 +30,7 @@ } type node struct { path []string - metaInfo *metainfo.MetaInfo + metadata *metainfo.Info FS *torrentFS InfoHash torrent.InfoHash } @@ -59,9 +58,9 @@ } if size < 0 { size = 0 } - infoHash := torrent.BytesInfoHash(fn.metaInfo.InfoHash) + infoHash := fn.InfoHash torrentOff := fn.TorrentOffset + req.Offset - // log.Print(torrentOff, size, fn.TorrentOffset) + log.Print(torrentOff, size, fn.TorrentOffset) if err := fn.FS.Client.PrioritizeDataRegion(infoHash, torrentOff, int64(size)); err != nil { panic(err) } @@ -112,7 +111,7 @@ } func (dn dirNode) ReadDir(intr fusefs.Intr) (des []fuse.Dirent, err fuse.Error) { names := map[string]bool{} - for _, fi := range dn.metaInfo.Files { + for _, fi := range dn.metadata.Files { if !isSubPath(dn.path, fi.Path) { continue } @@ -136,7 +135,7 @@ } func (dn dirNode) Lookup(name string, intr fusefs.Intr) (_node fusefs.Node, err fuse.Error) { var torrentOffset int64 - for _, fi := range dn.metaInfo.Files { + for _, fi := range dn.metadata.Files { if !isSubPath(dn.path, fi.Path) { torrentOffset += fi.Length continue @@ -169,26 +168,26 @@ attr.Mode = os.ModeDir | defaultMode return } -func isSingleFileTorrent(mi *metainfo.MetaInfo) bool { - return len(mi.Files) == 1 && mi.Files[0].Path == nil +func isSingleFileTorrent(md *metainfo.Info) bool { + return len(md.Files) == 0 } func (me rootNode) Lookup(name string, intr fusefs.Intr) (_node fusefs.Node, err fuse.Error) { - for _, _torrent := range me.fs.Client.Torrents() { - metaInfo := _torrent.MetaInfo - if metaInfo.Name == name { - __node := node{ - metaInfo: metaInfo, - FS: me.fs, - InfoHash: torrent.BytesInfoHash(metaInfo.InfoHash), - } - if isSingleFileTorrent(metaInfo) { - _node = fileNode{__node, uint64(metaInfo.Files[0].Length), 0} - } else { - _node = dirNode{__node} - } - break + for _, t := range me.fs.Client.Torrents() { + if t.Name() != name { + continue + } + __node := node{ + metadata: t.Info, + FS: me.fs, + InfoHash: t.InfoHash, + } + if isSingleFileTorrent(t.Info) { + _node = fileNode{__node, uint64(t.Info.Length), 0} + } else { + _node = dirNode{__node} } + break } if _node == nil { err = fuse.ENOENT @@ -198,7 +197,7 @@ } func (me rootNode) ReadDir(intr fusefs.Intr) (dirents []fuse.Dirent, err fuse.Error) { for _, _torrent := range me.fs.Client.Torrents() { - metaInfo := _torrent.MetaInfo + metaInfo := _torrent.Info dirents = append(dirents, fuse.Dirent{ Name: metaInfo.Name, Type: func() fuse.DirentType { diff --git a/fs/torrentfs_test.go b/fs/torrentfs_test.go index be85e473c2160548295ab28ed32cdc4f960b63c4..a9958372648a3103e267d57c332f2efc71c6a00b 100644 --- a/fs/torrentfs_test.go +++ b/fs/torrentfs_test.go @@ -2,6 +2,7 @@ package torrentfs import ( "bytes" + "fmt" "io/ioutil" "log" "net" @@ -15,7 +16,7 @@ "bazil.org/fuse" fusefs "bazil.org/fuse/fs" "bitbucket.org/anacrolix/go.torrent" - metainfo "github.com/nsf/libtorgo/torrent" + "github.com/anacrolix/libtorgo/metainfo" ) func TestTCPAddrString(t *testing.T) { @@ -83,6 +84,7 @@ DataDir: filepath.Join(layout.BaseDir, "incomplete"), DisableTrackers: true, } client.Start() + log.Printf("%+v", *layout.Metainfo) client.AddTorrent(layout.Metainfo) fs := New(&client) fuseConn, err := fuse.Mount(layout.MountDir) @@ -132,14 +134,19 @@ panic(err) } return conn }(), + DisableTrackers: true, } defer seeder.Listener.Close() seeder.Start() defer seeder.Stop() - seeder.AddTorrent(layout.Metainfo) + err = seeder.AddMagnet(fmt.Sprintf("magnet:?xt=urn:btih:%x", layout.Metainfo.InfoHash)) + if err != nil { + t.Fatal(err) + } leecher := torrent.Client{ DataDir: filepath.Join(layout.BaseDir, "download"), DownloadStrategy: &torrent.ResponsiveDownloadStrategy{}, + DisableTrackers: true, } leecher.Start() defer leecher.Stop() @@ -176,7 +183,9 @@ t.Fatal(fuseConn.MountError) } go func() { time.Sleep(10 * time.Second) - fuse.Unmount(mountDir) + if err := fuse.Unmount(mountDir); err != nil { + t.Log(err) + } }() content, err := ioutil.ReadFile(filepath.Join(mountDir, "greeting")) if err != nil { diff --git a/misc.go b/misc.go index 2e2436f9ad6434b9e9a8a9ef4e905120f2cac3ca..ab9cd9d2b9abb2e79e1395c88f33df478daf2436 100644 --- a/misc.go +++ b/misc.go @@ -2,16 +2,15 @@ package torrent import ( "bitbucket.org/anacrolix/go.torrent/mmap_span" + "bitbucket.org/anacrolix/go.torrent/peer_protocol" "crypto" "errors" - metainfo "github.com/nsf/libtorgo/torrent" + "github.com/anacrolix/libtorgo/metainfo" + "launchpad.net/gommap" "math/rand" "os" "path/filepath" "time" - - "bitbucket.org/anacrolix/go.torrent/peer_protocol" - "launchpad.net/gommap" ) const ( @@ -103,44 +102,22 @@ // Requested data not yet available. ErrDataNotReady = errors.New("data not ready") ) -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() (ret []string) { - for i := 0; i < len(me.mi.Pieces); i += 20 { - ret = append(ret, string(me.mi.Pieces[i:i+20])) +func upvertedSingleFileInfoFiles(info *metainfo.Info) []metainfo.FileInfo { + if len(info.Files) != 0 { + return info.Files } - return -} -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 + return []metainfo.FileInfo{{Length: info.Length, Path: nil}} } -func mmapTorrentData(md MetaData, location string) (mms mmap_span.MMapSpan, err error) { +func mmapTorrentData(md *metainfo.Info, location string) (mms mmap_span.MMapSpan, err error) { defer func() { if err != nil { mms.Close() mms = nil } }() - for _, miFile := range md.Files() { - fileName := filepath.Join(append([]string{location, md.Name()}, miFile.Path...)...) + for _, miFile := range upvertedSingleFileInfoFiles(md) { + fileName := filepath.Join(append([]string{location, md.Name}, miFile.Path...)...) err = os.MkdirAll(filepath.Dir(fileName), 0777) if err != nil { return diff --git a/peer_protocol/protocol.go b/peer_protocol/protocol.go index 9900a033c3509b94471be2630076aa390fd85fe6..52a952fda8ae60e6cdb2b1a0ed7184fb60d2a58e 100644 --- a/peer_protocol/protocol.go +++ b/peer_protocol/protocol.go @@ -36,6 +36,10 @@ Cancel // 8 Extended = 20 HandshakeExtendedID = 0 + + RequestMetadataExtensionMsgType = 0 + DataMetadataExtensionMsgType = 1 + RejectMetadataExtensionMsgType = 2 ) type Message struct { diff --git a/testutil/testutil.go b/testutil/testutil.go index 4a150b08f4982e31d03ea9aabd42489f5d15784e..f3616b6a7c4c73c10f8538432a2eee7bbc102c16 100644 --- a/testutil/testutil.go +++ b/testutil/testutil.go @@ -6,14 +6,13 @@ package testutil import ( + "bytes" "io" "io/ioutil" "os" "path/filepath" - metainfo "github.com/nsf/libtorgo/torrent" - - "bytes" + "github.com/anacrolix/libtorgo/metainfo" ) const GreetingFileContents = "hello, world\n" diff --git a/torrent.go b/torrent.go index 18ddc6db8bb22005bed6cba1412a8a7128490ff9..c5a4885641f15bad1820b14f3a72a8970255526b 100644 --- a/torrent.go +++ b/torrent.go @@ -1,16 +1,17 @@ package torrent import ( + "bitbucket.org/anacrolix/go.torrent/mmap_span" + pp "bitbucket.org/anacrolix/go.torrent/peer_protocol" + "bitbucket.org/anacrolix/go.torrent/tracker" "container/list" "fmt" + "github.com/anacrolix/libtorgo/bencode" + "github.com/anacrolix/libtorgo/metainfo" "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" ) func (t *torrent) PieceNumPendingBytes(index pp.Integer) (count pp.Integer) { @@ -29,7 +30,7 @@ type torrent struct { InfoHash InfoHash Pieces []*piece Data mmap_span.MMapSpan - Info MetaData + Info *metainfo.Info Conns []*connection Peers []Peer Priorities *list.List @@ -39,14 +40,73 @@ Trackers [][]tracker.Client lastReadPiece int DisplayName string MetaData []byte - MetaDataHave []bool + metadataHave []bool +} + +func (t *torrent) InvalidateMetadata() { + for i := range t.metadataHave { + t.metadataHave[i] = false + } + t.Info = nil } -func (t *torrent) GotAllMetadataPieces() bool { - if t.MetaDataHave == nil { +func (t *torrent) SaveMetadataPiece(index int, data []byte) { + if t.haveInfo() { + return + } + copy(t.MetaData[(1<<14)*index:], data) + t.metadataHave[index] = true +} + +func (t *torrent) MetadataPieceCount() int { + return (len(t.MetaData) + (1 << 14) - 1) / (1 << 14) +} + +func (t *torrent) HaveMetadataPiece(piece int) bool { + return t.haveInfo() || t.metadataHave[piece] +} + +func (t *torrent) metadataSizeKnown() bool { + return t.MetaData != nil +} + +func (t *torrent) metadataSize() int { + return len(t.MetaData) +} + +func infoPieceHashes(info *metainfo.Info) (ret []string) { + for i := 0; i < len(info.Pieces); i += 20 { + ret = append(ret, string(info.Pieces[i:i+20])) + } + return +} + +func (t *torrent) setMetadata(md metainfo.Info, dataDir string, infoBytes []byte) (err error) { + t.Info = &md + t.MetaData = infoBytes + t.metadataHave = nil + t.Data, err = mmapTorrentData(&md, dataDir) + if err != nil { + return + } + for _, hash := range infoPieceHashes(&md) { + piece := &piece{} + copyHashSum(piece.Hash[:], []byte(hash)) + t.Pieces = append(t.Pieces, piece) + t.pendAllChunkSpecs(pp.Integer(len(t.Pieces) - 1)) + } + t.Priorities = list.New() + return +} + +func (t *torrent) HaveAllMetadataPieces() bool { + if t.haveInfo() { + return true + } + if t.metadataHave == nil { return false } - for _, have := range t.MetaDataHave { + for _, have := range t.metadataHave { if !have { return false } @@ -54,22 +114,19 @@ } return true } -func (t *torrent) SetMetaDataSize(bytes int64) { +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)) + t.metadataHave = make([]bool, (bytes+(1<<14)-1)/(1<<14)) } func (t *torrent) Name() string { - if t.Info == nil { + if !t.haveInfo() { return t.DisplayName } - return t.Info.Name() + return t.Info.Name } func (t *torrent) pieceStatusChar(index int) byte { @@ -88,6 +145,30 @@ return '.' } } +func (t *torrent) metadataPieceSize(piece int) int { + return metadataPieceSize(len(t.MetaData), piece) +} + +func (t *torrent) NewMetadataExtensionMessage(c *connection, msgType int, piece int, data []byte) pp.Message { + d := map[string]int{ + "msg_type": msgType, + "piece": piece, + } + if data != nil { + d["total_size"] = len(t.MetaData) + } + p, err := bencode.Marshal(d) + if err != nil { + panic(err) + } + return pp.Message{ + Type: pp.Extended, + ExtendedID: byte(c.PeerExtensionIDs["ut_metadata"]), + ExtendedPayload: append(p, data...), + } + +} + func (t *torrent) WriteStatus(w io.Writer) { fmt.Fprint(w, "Pieces: ") for index := range t.Pieces { @@ -136,7 +217,7 @@ return } func (t *torrent) UsualPieceSize() int { - return int(t.Info.PieceLength()) + return int(t.Info.PieceLength) } func (t *torrent) LastPieceSize() int { @@ -144,7 +225,7 @@ return int(t.PieceLength(pp.Integer(t.NumPieces() - 1))) } func (t *torrent) NumPieces() int { - return t.Info.PieceCount() + return len(t.Info.Pieces) / 20 } func (t *torrent) NumPiecesCompleted() (num int) { @@ -208,16 +289,16 @@ return } func (t *torrent) requestOffset(r request) int64 { - return torrentRequestOffset(t.Length(), t.Info.PieceLength(), r) + return torrentRequestOffset(t.Length(), int64(t.UsualPieceSize()), 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.Info.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.Info.PieceLength()+begin) + _, err = t.Data.WriteAt(data, int64(piece)*t.Info.PieceLength+begin) return } @@ -233,7 +314,7 @@ piece := t.Pieces[index] if piece.PendingChunkSpecs == nil { piece.PendingChunkSpecs = make( map[chunkSpec]struct{}, - (t.Info.PieceLength()+chunkSize-1)/chunkSize) + (t.Info.PieceLength+chunkSize-1)/chunkSize) } c := chunkSpec{ Begin: 0, @@ -258,21 +339,22 @@ } func (t *torrent) PieceLength(piece pp.Integer) (len_ pp.Integer) { if int(piece) == t.NumPieces()-1 { - len_ = pp.Integer(t.Data.Size() % t.Info.PieceLength()) + len_ = pp.Integer(t.Data.Size() % t.Info.PieceLength) } if len_ == 0 { - len_ = pp.Integer(t.Info.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.Info.PieceLength(), t.Info.PieceLength()) + n, err := t.Data.WriteSectionTo(hash, int64(piece)*t.Info.PieceLength, t.Info.PieceLength) if err != nil { panic(err) } if pp.Integer(n) != t.PieceLength(piece) { + log.Print(t.Info) panic(fmt.Sprintf("hashed wrong number of bytes: expected %d; did %d; piece %d", t.PieceLength(piece), n, piece)) } copyHashSum(ps[:], hash.Sum(nil))