diff --git a/down/bt/client_test.go b/down/bt/client_test.go index 5b5e1708..3e709726 100644 --- a/down/bt/client_test.go +++ b/down/bt/client_test.go @@ -6,5 +6,19 @@ import ( func TestClient_AddTorrent(t *testing.T) { client := NewClient() - client.AddTorrent("testdata/Game.of.Thrones.S08E05.720p.WEB.H264-MEMENTO.torrent") + metaInfo, err := ParseFromFile("testdata/Game.of.Thrones.S08E05.720p.WEB.H264-MEMENTO.torrent") + if err != nil { + panic(err) + } + torrent := &Torrent{ + client: client, + MetaInfo: metaInfo, + } + torrent.MetaInfo.Announce = "udp://tracker.opentrackr.org:1337/announce" + torrent.MetaInfo.AnnounceList = [][]string{} + err = torrent.Tracker() + if err != nil { + panic(err) + return + } } diff --git a/down/bt/metainfo.go b/down/bt/metainfo.go index 9917f6d6..2ff5af0f 100644 --- a/down/bt/metainfo.go +++ b/down/bt/metainfo.go @@ -60,7 +60,7 @@ func ParseFromFile(path string) (*MetaInfo, error) { var metaInfo MetaInfo json.Unmarshal(bytes, &metaInfo) if metaInfo.Info != nil { - // Encode info hash + // encode info hash info := btDecode["info"].(map[string]interface{}) infoBtEncode := bencode.Encode(info) metaInfo.InfoHash = sha1.Sum(infoBtEncode[:]) diff --git a/down/bt/peer.go b/down/bt/peer.go index 4f6d36b3..776d4ab2 100644 --- a/down/bt/peer.go +++ b/down/bt/peer.go @@ -34,6 +34,15 @@ type Peer struct { Torrent *Torrent conn net.Conn + + // this client is choking the peer + AmChoking bool + // this client is interested in the peer + AmInterested bool + // peer is choking this client + PeerChoking bool + // peer is interested in this client + PeerInterested bool } func (peer *Peer) Address() string { @@ -56,7 +65,19 @@ type Message struct { Payload []byte } -func NewHandshake(reserved [8]byte, infoHash [20]byte, peerID [20]byte) *Handshake { +type MessageBitfield []bool + +func decodeBitfield(payload []byte) MessageBitfield { + bits := make([]bool, len(payload)*8) + for i, b := range payload { + for j := 0; j < 8; j++ { + bits[i*8+j] = b&(1< 0 + } + } + return bits +} + +func newHandshake(reserved [8]byte, infoHash [20]byte, peerID [20]byte) *Handshake { var arr [ProtocolIdentifierLength]byte copy(arr[:], ProtocolIdentifier) return &Handshake{ @@ -112,6 +133,11 @@ func (peer *Peer) DoDownload() error { if err != nil { return err } + // handshake success,init status + peer.AmChoking = true + peer.AmInterested = false + peer.PeerChoking = true + peer.PeerInterested = false scanner := bufio.NewScanner(conn) scanner.Split(splitMessage) @@ -124,16 +150,22 @@ func (peer *Peer) DoDownload() error { } else { switch MessageType(buf[4]) { case Choke: + peer.PeerChoking = true break case Unchoke: + peer.PeerChoking = false break case Interested: + peer.PeerInterested = true break case NotInterested: + peer.PeerInterested = false break case Have: break case Bitfield: + bitfield := decodeBitfield(message.Payload) + fmt.Println(bitfield) break case Request: break @@ -229,7 +261,7 @@ func (peer *Peer) doHandshake(conn net.Conn) error { /* reserved[5] = 0x10 reserved[6] = 0x0 reserved[7] = 0x5*/ - handshakeReq := NewHandshake(reserved, metaInfo.InfoHash, peerID) + handshakeReq := newHandshake(reserved, metaInfo.InfoHash, peerID) buf, err := handshakeReq.encode() if err != nil { return err @@ -252,6 +284,11 @@ func (peer *Peer) doHandshake(conn net.Conn) error { return nil } +// Message protocol +// length prefix| message ID | payload +// 4-byte | 1-byte | remaining bytes +// 100 | 5 | 99-bytes +// https://wiki.theory.org/index.php/BitTorrentSpecification#Messages func splitMessage(data []byte, atEOF bool) (advance int, token []byte, err error) { if !atEOF && len(data) > 4 { length := int(binary.BigEndian.Uint32(data)) diff --git a/down/bt/peer_test.go b/down/bt/peer_test.go new file mode 100644 index 00000000..69abd297 --- /dev/null +++ b/down/bt/peer_test.go @@ -0,0 +1,41 @@ +package bt + +import ( + "fmt" + "reflect" + "testing" +) + +func Test_decodeBitfield(t *testing.T) { + fmt.Printf("%08b", 0xFF) + type args struct { + payload []byte + } + tests := []struct { + name string + args args + want MessageBitfield + }{ + { + "case-11111111", + args{ + []byte{0xFF}, + }, + []bool{true, true, true, true, true, true, true, true}, + }, + { + "case-10101010", + args{ + []byte{0xAA}, + }, + []bool{true, false, true, false, true, false, true, false}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := decodeBitfield(tt.args.payload); !reflect.DeepEqual(got, tt.want) { + t.Errorf("decodeBitfield() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/down/bt/torrent.go b/down/bt/torrent.go index 65dd9408..136350e3 100644 --- a/down/bt/torrent.go +++ b/down/bt/torrent.go @@ -2,6 +2,7 @@ package bt import ( "fmt" + "io" ) type Torrent struct { @@ -11,14 +12,49 @@ type Torrent struct { Peers []Peer } +// Handshake of Peer wire protocol +// Per https://wiki.theory.org/index.php/BitTorrentSpecification#Handshake +func (torrent *Torrent) doHandshake(peer *Peer) error { + conn, err := dialMse(peer.Address(), peer.Torrent.MetaInfo.InfoHash) + if err != nil { + return err + } + defer conn.Close() + + metaInfo := peer.Torrent.MetaInfo + peerID := peer.Torrent.client.PeerID + + reserved := [8]byte{} + /* reserved[5] = 0x10 + reserved[6] = 0x0 + reserved[7] = 0x5*/ + handshakeReq := newHandshake(reserved, metaInfo.InfoHash, peerID) + buf, err := handshakeReq.encode() + if err != nil { + return err + } + conn.Write(buf) + + var read [68]byte + _, err = io.ReadFull(conn, read[:]) + if err != nil { + return err + } + handshakeRes := new(Handshake) + err = handshakeRes.decode(read[:]) + if err != nil { + return err + } + if handshakeRes.InfoHash != handshakeReq.InfoHash { + return err + } + return nil +} + func (torrent *Torrent) Download() { - t := NewTracker(torrent) - peers, err := t.Tracker() + err := torrent.Tracker() if err != nil { fmt.Println(err) return } - - peers[0].Torrent = torrent - peers[0].DoDownload() } diff --git a/down/bt/tracker.go b/down/bt/tracker.go index 23939b75..986c1e38 100644 --- a/down/bt/tracker.go +++ b/down/bt/tracker.go @@ -25,14 +25,6 @@ const ( udpActionError = 3 ) -type Tracker struct { - torrent *Torrent -} - -func NewTracker(torrent *Torrent) *Tracker { - return &Tracker{torrent: torrent} -} - /* Offset Size Name Value 0 64-bit integer protocol_id 0x41727101980 // magic constant @@ -55,7 +47,7 @@ func newUdpConnectRequest() *udpConnectRequest { } } -func (req *udpConnectRequest) Encode() []byte { +func (req *udpConnectRequest) encode() []byte { buf := make([]byte, 16) binary.BigEndian.PutUint64(buf[:8], req.protocolId) binary.BigEndian.PutUint32(buf[8:12], req.action) @@ -186,9 +178,9 @@ func newUdpAnnounceResponse(buf []byte) *udpAnnounceResponse { return response } -func (tracker *Tracker) connect(conn *net.UDPConn, timeout int64) (response *udpConnectResponse, err error) { +func (torrent *Torrent) connect(conn *net.UDPConn, timeout int64) (response *udpConnectResponse, err error) { request := newUdpConnectRequest() - buf := request.Encode() + buf := request.encode() conn.SetReadDeadline(time.Now().Add(time.Second * time.Duration(timeout))) conn.Write(buf) len, err := conn.Read(buf) @@ -203,9 +195,7 @@ func (tracker *Tracker) connect(conn *net.UDPConn, timeout int64) (response *udp return } -func (tracker *Tracker) announce(conn *net.UDPConn, timeout int64, connectionId uint64) (response *udpAnnounceResponse, err error) { - torrent := tracker.torrent - +func (torrent *Torrent) announce(conn *net.UDPConn, timeout int64, connectionId uint64) (response *udpAnnounceResponse, err error) { request := newUdpAnnounceRequest(connectionId) request.infoHash = torrent.MetaInfo.InfoHash request.peerID = torrent.client.PeerID @@ -215,7 +205,7 @@ func (tracker *Tracker) announce(conn *net.UDPConn, timeout int64, connectionId request.event = 0 request.ip = 0 request.key = 0 - request.numWant = -1 + request.numWant = 50 request.port = 6882 encode := request.encode() conn.SetReadDeadline(time.Now().Add(time.Second * time.Duration(timeout))) @@ -230,8 +220,9 @@ func (tracker *Tracker) announce(conn *net.UDPConn, timeout int64, connectionId return } -func (tracker *Tracker) Tracker() (peers []Peer, err error) { - metaInfo := tracker.torrent.MetaInfo +// 通过tracker服务器获取peer信息 +func (torrent *Torrent) Tracker() (err error) { + metaInfo := torrent.MetaInfo checkAnnounceList := func(announceList [][]string) bool { if len(announceList) > 0 { @@ -244,11 +235,12 @@ func (tracker *Tracker) Tracker() (peers []Peer, err error) { return false } + // 只要获取到了peers就直接返回 if checkAnnounceList(metaInfo.AnnounceList) { for _, announceArr := range metaInfo.AnnounceList { if len(announceArr) > 0 { for _, announce := range announceArr { - peers, err = tracker.doTracker(announce) + torrent.Peers, err = torrent.doTracker(announce) if err == nil { return } @@ -256,24 +248,21 @@ func (tracker *Tracker) Tracker() (peers []Peer, err error) { } } } else { - peers, err = tracker.doTracker(metaInfo.Announce) - if err == nil { - return - } + torrent.Peers, err = torrent.doTracker(metaInfo.Announce) } return } -func (tracker *Tracker) doTracker(announce string) (peers []Peer, err error) { +func (torrent *Torrent) doTracker(announce string) (peers []Peer, err error) { if announce != "" { url, _ := url.Parse(announce) switch url.Scheme { case "http", "https": // TODO UDP test break - return tracker.httpTracker(url) + return torrent.httpTracker(url) case "udp", "udp4", "udp6": - return tracker.udpTracker(url) + return torrent.udpTracker(url) default: return nil, errors.New("unsupported protocol") } @@ -282,9 +271,9 @@ func (tracker *Tracker) doTracker(announce string) (peers []Peer, err error) { } // http://bittorrent.org/beps/bep_0003.html#trackers -func (tracker *Tracker) httpTracker(url *url.URL) (peers []Peer, err error) { - metaInfo := tracker.torrent.MetaInfo - peerID := tracker.torrent.client.PeerID +func (torrent *Torrent) httpTracker(url *url.URL) (peers []Peer, err error) { + metaInfo := torrent.MetaInfo + peerID := torrent.client.PeerID query := url.Query() query.Add("info_hash", string(metaInfo.InfoHash[:])) @@ -319,7 +308,7 @@ func (tracker *Tracker) httpTracker(url *url.URL) (peers []Peer, err error) { } // http://bittorrent.org/beps/bep_0015.html -func (tracker *Tracker) udpTracker(url *url.URL) (peers []Peer, err error) { +func (torrent *Torrent) udpTracker(url *url.URL) (peers []Peer, err error) { conn, err := dial(url) if err != nil { return @@ -336,7 +325,7 @@ func (tracker *Tracker) udpTracker(url *url.URL) (peers []Peer, err error) { timeout := int64(udpConnectTimeout * math.Pow(2, float64(n))) switch state { case 0: - connectResponse, err = tracker.connect(conn, timeout) + connectResponse, err = torrent.connect(conn, timeout) if err != nil { if oe, ok := err.(*net.OpError); ok && oe.Timeout() { break @@ -348,7 +337,7 @@ func (tracker *Tracker) udpTracker(url *url.URL) (peers []Peer, err error) { state = 1 break case 1: - announceResponse, err = tracker.announce(conn, timeout, connectResponse.connectionId) + announceResponse, err = torrent.announce(conn, timeout, connectResponse.connectionId) if err != nil { if oe, ok := err.(*net.OpError); ok && oe.Timeout() { break diff --git a/down/bt/tracker_test.go b/down/bt/tracker_test.go new file mode 100644 index 00000000..dde5cdd3 --- /dev/null +++ b/down/bt/tracker_test.go @@ -0,0 +1,24 @@ +package bt + +import ( + "testing" +) + +func TestTracker_Tracker(t *testing.T) { + client := NewClient() + metaInfo, err := ParseFromFile("testdata/Game.of.Thrones.S08E05.720p.WEB.H264-MEMENTO.torrent") + if err != nil { + panic(err) + } + torrent := &Torrent{ + client: client, + MetaInfo: metaInfo, + } + torrent.MetaInfo.Announce = "udp://tracker.opentrackr.org:1337/announce" + torrent.MetaInfo.AnnounceList = [][]string{} + err = torrent.Tracker() + if err != nil { + panic(err) + return + } +}