]> Sergey Matveev's repositories - btrtrc.git/commitdiff
Begin adding magnet and ut_metadata support
authorMatt Joiner <anacrolix@gmail.com>
Thu, 26 Jun 2014 14:57:07 +0000 (00:57 +1000)
committerMatt Joiner <anacrolix@gmail.com>
Thu, 26 Jun 2014 14:57:07 +0000 (00:57 +1000)
client.go
cmd/dht-server/main.go
cmd/torrent/main.go
connection.go
dht/dht.go
magnet.go [new file with mode: 0644]
misc.go
peer_protocol/protocol.go
torrent.go

index 4888984353d2424febdd8fafd9995ec599ec5786..13589a0d5a70a067621bf821d45564203378e666 100644 (file)
--- 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)
-       if !me.DisableTrackers {
-               go me.announceTorrent(torrent)
+       t.DisplayName = m.DisplayName
+       cl.mu.Lock()
+       defer cl.mu.Unlock()
+       err = cl.addTorrent(t)
+       if err != nil {
+               t.Close()
        }
-       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
+func (me *Client) addTorrent(t *torrent) (err error) {
+       if _, ok := me.torrents[t.InfoHash]; ok {
+               err = fmt.Errorf("torrent infohash collision")
+               return
        }
-       go func() {
-               for i := range torrent.Pieces {
-                       me.verifyPiece(torrent, pp.Integer(i))
-               }
-       }()
+       me.torrents[t.InfoHash] = t
+       if !me.DisableTrackers {
+               go me.announceTorrent(t)
+       }
+       return
+}
 
-       return nil
+// 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
+       }
+       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
        }
 
index b18dfc46bda217d38062985f259745aaf656eacc..667a8c8a4b95903968095b94e0e6c514842fd271 100644 (file)
@@ -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()
index 59c1d801c4d3a034a33dc83f5a508750dc4b6197..6874ef678cc8e6347f77fcdc0fca307bb4aa44e2 100644 (file)
@@ -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)
-               }
-               err = client.AddTorrent(metaInfo)
-               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)
                }
-               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
                        }
index c56329f6ebec9640da1c8b8b19efdf733aca5a58..7a116b2e62e153d7777d4962199713b854b880a7 100644 (file)
@@ -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 {
index 717c45ad963f04be1e85c753f5dceb2dedbe567d..0bc6c4f4f3f2c841af647027625eaf2fdbc07aae 100644 (file)
@@ -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 (file)
index 0000000..4a842c7
--- /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 c53976fab111780ed960fa0e54e983c2b46c78ed..3ffb2852fff18bed81b0feb5ef808bf978a1e743 100644 (file)
--- 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
+}
index 90b01359e6db8b539cb532d8526375e88056d82e..9900a033c3509b94471be2630076aa390fd85fe6 100644 (file)
@@ -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)
        }
index 1e3cf91bbb79e419dfa02e3d2e017fc603bf234f..e051d8b96df3e7cb8690d0fd79eae02efcea86a3 100644 (file)
@@ -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
 }