From 4763ff129f77dc811cea9dda154522c120f58924 Mon Sep 17 00:00:00 2001 From: liwei Date: Thu, 25 Mar 2021 20:00:50 +0800 Subject: [PATCH] test: remove bt --- pkg/protocol/bt/client.go | 57 --- pkg/protocol/bt/client_test.go | 21 - pkg/protocol/bt/dht/ping.go | 34 -- pkg/protocol/bt/dht/ping_test.go | 7 - pkg/protocol/bt/metainfo/metainfo.go | 144 ------ pkg/protocol/bt/metainfo/metainfo_test.go | 22 - pkg/protocol/bt/peer/handshake.go | 51 -- pkg/protocol/bt/peer/message/message.go | 143 ------ pkg/protocol/bt/peer/message/msg_bitfield.go | 68 --- .../bt/peer/message/msg_bitfield_test.go | 111 ----- pkg/protocol/bt/peer/message/msg_choke.go | 14 - .../bt/peer/message/msg_interested.go | 14 - pkg/protocol/bt/peer/message/msg_keepalive.go | 16 - .../bt/peer/message/msg_not_interested.go | 14 - pkg/protocol/bt/peer/message/msg_piece.go | 42 -- pkg/protocol/bt/peer/message/msg_request.go | 43 -- pkg/protocol/bt/peer/message/msg_unchoke.go | 14 - pkg/protocol/bt/peer/peer.go | 30 -- ...rones.S08E05.720p.WEB.H264-MEMENTO.torrent | Bin 28480 -> 0 bytes pkg/protocol/bt/testdata/office.torrent | Bin 12575 -> 0 bytes pkg/protocol/bt/torrent/main/main.go | 74 --- pkg/protocol/bt/torrent/peer_conn.go | 464 ------------------ pkg/protocol/bt/torrent/peer_conn_test.go | 122 ----- pkg/protocol/bt/torrent/peer_pool.go | 99 ---- pkg/protocol/bt/torrent/piece_state.go | 109 ---- pkg/protocol/bt/torrent/torrent.go | 83 ---- pkg/protocol/bt/torrent/torrent_test.go | 77 --- pkg/protocol/bt/tracker/errors.go | 29 -- pkg/protocol/bt/tracker/tracker.go | 412 ---------------- pkg/protocol/bt/tracker/tracker_test.go | 22 - pkg/protocol/http/client.go | 171 ------- pkg/protocol/http/client_test.go | 154 ------ pkg/protocol/http/request.go | 8 - pkg/protocol/http/response.go | 7 - 34 files changed, 2676 deletions(-) delete mode 100644 pkg/protocol/bt/client.go delete mode 100644 pkg/protocol/bt/client_test.go delete mode 100644 pkg/protocol/bt/dht/ping.go delete mode 100644 pkg/protocol/bt/dht/ping_test.go delete mode 100644 pkg/protocol/bt/metainfo/metainfo.go delete mode 100644 pkg/protocol/bt/metainfo/metainfo_test.go delete mode 100644 pkg/protocol/bt/peer/handshake.go delete mode 100644 pkg/protocol/bt/peer/message/message.go delete mode 100644 pkg/protocol/bt/peer/message/msg_bitfield.go delete mode 100644 pkg/protocol/bt/peer/message/msg_bitfield_test.go delete mode 100644 pkg/protocol/bt/peer/message/msg_choke.go delete mode 100644 pkg/protocol/bt/peer/message/msg_interested.go delete mode 100644 pkg/protocol/bt/peer/message/msg_keepalive.go delete mode 100644 pkg/protocol/bt/peer/message/msg_not_interested.go delete mode 100644 pkg/protocol/bt/peer/message/msg_piece.go delete mode 100644 pkg/protocol/bt/peer/message/msg_request.go delete mode 100644 pkg/protocol/bt/peer/message/msg_unchoke.go delete mode 100644 pkg/protocol/bt/peer/peer.go delete mode 100644 pkg/protocol/bt/testdata/Game.of.Thrones.S08E05.720p.WEB.H264-MEMENTO.torrent delete mode 100644 pkg/protocol/bt/testdata/office.torrent delete mode 100644 pkg/protocol/bt/torrent/main/main.go delete mode 100644 pkg/protocol/bt/torrent/peer_conn.go delete mode 100644 pkg/protocol/bt/torrent/peer_conn_test.go delete mode 100644 pkg/protocol/bt/torrent/peer_pool.go delete mode 100644 pkg/protocol/bt/torrent/piece_state.go delete mode 100644 pkg/protocol/bt/torrent/torrent.go delete mode 100644 pkg/protocol/bt/torrent/torrent_test.go delete mode 100644 pkg/protocol/bt/tracker/errors.go delete mode 100644 pkg/protocol/bt/tracker/tracker.go delete mode 100644 pkg/protocol/bt/tracker/tracker_test.go delete mode 100644 pkg/protocol/http/client.go delete mode 100644 pkg/protocol/http/client_test.go delete mode 100644 pkg/protocol/http/request.go delete mode 100644 pkg/protocol/http/response.go diff --git a/pkg/protocol/bt/client.go b/pkg/protocol/bt/client.go deleted file mode 100644 index 17c907ee..00000000 --- a/pkg/protocol/bt/client.go +++ /dev/null @@ -1,57 +0,0 @@ -package bt - -import ( - "crypto/rand" - "github.com/monkeyWie/gopeed-core/pkg/protocol/bt/metainfo" - "github.com/monkeyWie/gopeed-core/pkg/protocol/bt/torrent" - "net/url" -) - -type Client struct { - PeerID [20]byte - - // torrents []torrent.Torrent -} - -func NewClient() *Client { - client := &Client{} - - // 生成Peer ID,规则为前三位固定字母(-GP)+SemVer(xyz)+End(-),后面随机生成 - // 参考:https://wiki.theory.org/index.php/BitTorrentSpecification#peer_id - peerID := [20]byte{'-', 'G', 'P', '0', '0', '1', '-'} - _, err := rand.Read(peerID[7:]) - if err != nil { - panic(err) - } - client.PeerID = peerID - - return client -} - -func (client *Client) AddTorrent(rawurl string) error { - parse, err := url.Parse(rawurl) - if err != nil { - return err - } - var metaInfo *metainfo.MetaInfo - switch parse.Scheme { - case "magnet": - // TODO 磁力链接解析 - default: - metaInfo, err = metainfo.ParseFromFile(rawurl) - break - } - if err != nil { - return err - } - - torrent := torrent.Torrent{ - // client: client, - MetaInfo: metaInfo, - } - // client.torrents = append(client.torrents, torrent) - - torrent.Download("") - - return nil -} diff --git a/pkg/protocol/bt/client_test.go b/pkg/protocol/bt/client_test.go deleted file mode 100644 index 1a4c0747..00000000 --- a/pkg/protocol/bt/client_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package bt - -import ( - "testing" -) - -func TestClient_AddTorrent(t *testing.T) { - /*client := NewClient() - metaInfo, err := metainfo.ParseFromFile("testdata/Game.of.Thrones.S08E05.720p.WEB.H264-MEMENTO.torrent") - if err != nil { - panic(err) - } - torrent := & - 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/pkg/protocol/bt/dht/ping.go b/pkg/protocol/bt/dht/ping.go deleted file mode 100644 index 3a054be6..00000000 --- a/pkg/protocol/bt/dht/ping.go +++ /dev/null @@ -1,34 +0,0 @@ -package dht - -import ( - "github.com/marksamman/bencode" - "net" -) - -func ping() string { - data := map[string]interface{}{ - "t": "aa", - "y": "q", - "q": "ping", - "a": map[string]interface{}{"id": "abcdefghij0123456789"}, - } - req := bencode.Encode(data) - // session.add_dht_router("router.utorrent.com", 6881) - // session.add_dht_router("router.bittorrent.com", 6881) - // session.add_dht_router("dht.transmissionbt.com", 6881) - // session.add_dht_router("router.bitcomet.com", 6881) - // session.add_dht_router("dht.aelitis.com", 6881) - dial, err := net.Dial("udp", "router.utorrent.com:6881") - if err != nil { - panic(err) - } - dial.Write(req) - - reps := make([]byte, 65535) - - n, err := dial.Read(reps) - if err != nil { - panic(err) - } - return string(reps[:n]) -} diff --git a/pkg/protocol/bt/dht/ping_test.go b/pkg/protocol/bt/dht/ping_test.go deleted file mode 100644 index ea0e1e79..00000000 --- a/pkg/protocol/bt/dht/ping_test.go +++ /dev/null @@ -1,7 +0,0 @@ -package dht - -import "testing" - -func Test_ping(t *testing.T) { - println(ping()) -} diff --git a/pkg/protocol/bt/metainfo/metainfo.go b/pkg/protocol/bt/metainfo/metainfo.go deleted file mode 100644 index 8f9c55d9..00000000 --- a/pkg/protocol/bt/metainfo/metainfo.go +++ /dev/null @@ -1,144 +0,0 @@ -package metainfo - -import ( - "crypto/sha1" - "encoding/json" - "os" - - "github.com/marksamman/bencode" -) - -type MetaInfo struct { - // tracker URL - // http://bittorrent.org/beps/bep_0003.html#metainfo-files - Announce string `json:"announce"` - // tracker URL - // http://bittorrent.org/beps/bep_0012.html - AnnounceList [][]string `json:"announce-list"` - Comment string `json:"comment"` - CreatedBy string `json:"created by"` - CreationDate int64 `json:"creation date"` - Encoding string `json:"encoding"` - // http://bittorrent.org/beps/bep_0019.html - UrlList []string `json:"url-list"` - Info *Info `json:"info"` - - infoHash [20]byte - totalSize uint64 - fileDetails []*FileDetail -} - -type Info struct { - Name string `json:"name"` - PieceLength int `json:"piece length"` - Pieces [][20]byte - // 单个文件时,length为该文件的长度 - Length uint64 `json:"length"` - // 多个文件 - Files []*File `json:"files"` -} - -type File struct { - Length uint64 `json:"length"` - Path []string `json:"path"` -} - -type FileDetail struct { - Length uint64 - Path []string - Begin int64 - End int64 -} - -type PieceMapping struct { - file int - begin int64 - end int64 -} - -func ParseFromFile(path string) (*MetaInfo, error) { - file, err := os.Open(path) - if err != nil { - return nil, err - } - // http://bittorrent.org/beps/bep_0003.html#bencoding - btDecode, err := bencode.Decode(file) - if err != nil { - return nil, err - } - bytes, err := json.Marshal(btDecode) - if err != nil { - return nil, err - } - var metaInfo MetaInfo - json.Unmarshal(bytes, &metaInfo) - if metaInfo.Info != nil { - // encode info hash - info := btDecode["info"].(map[string]interface{}) - infoBtEncode := bencode.Encode(info) - metaInfo.infoHash = sha1.Sum(infoBtEncode[:]) - // Split pieces hash - pieces := info["pieces"].(string) - if pieces != "" { - metaInfo.Info.Pieces = make([][20]byte, len(pieces)/20) - // pieces parse - for i := range metaInfo.Info.Pieces { - begin := i * 20 - metaInfo.Info.Pieces[i] = [20]byte{} - copy(metaInfo.Info.Pieces[i][:], pieces[begin:begin+20]) - } - } - } - calcFileSize(&metaInfo) - return &metaInfo, nil -} - -// 获取某个分片的大小 -func (metaInfo *MetaInfo) GetPieceLength(index int) int { - // 是否为最后一个分片 - if index == len(metaInfo.Info.Pieces)-1 { - size := metaInfo.totalSize % uint64(metaInfo.Info.PieceLength) - if size > 0 { - return int(size) - } - } - return metaInfo.Info.PieceLength -} - -// 获取所有文件的大小 -func (metaInfo *MetaInfo) GetTotalSize() uint64 { - return metaInfo.totalSize -} - -// 获取info hash -func (metaInfo *MetaInfo) GetInfoHash() [20]byte { - return metaInfo.infoHash -} - -// 获取所有文件的开始偏移字节 -func (metaInfo *MetaInfo) GetFileDetails() []*FileDetail { - return metaInfo.fileDetails -} - -// 计算所有文件的大小 -func calcFileSize(metaInfo *MetaInfo) { - if metaInfo.Info != nil { - fileCount := len(metaInfo.Info.Files) - if fileCount > 0 { - metaInfo.fileDetails = make([]*FileDetail, fileCount) - var length uint64 - for i, f := range metaInfo.Info.Files { - metaInfo.fileDetails[i] = &FileDetail{ - Length: f.Length, - Path: f.Path, - Begin: int64(length), - End: int64(length + f.Length), - } - length += f.Length - } - metaInfo.totalSize = length - } else { - metaInfo.totalSize = metaInfo.Info.Length - } - } -} diff --git a/pkg/protocol/bt/metainfo/metainfo_test.go b/pkg/protocol/bt/metainfo/metainfo_test.go deleted file mode 100644 index e940abfd..00000000 --- a/pkg/protocol/bt/metainfo/metainfo_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package metainfo - -import ( - "encoding/hex" - "fmt" - "testing" -) - -func TestParseFromFile(t *testing.T) { - // metaInfo, err := ParseFromFile("../testdata/Game.of.Thrones.S08E05.720p.WEB.H264-MEMENTO.torrent") - metaInfo, err := ParseFromFile("E:\\bt\\win10.torrent") - if err != nil { - panic(err) - } - fmt.Printf("%+v\n", metaInfo.AnnounceList) - for _, file := range metaInfo.Info.Files { - fmt.Printf("%+v\n", file) - } - fmt.Printf("%+v\n", metaInfo.Info.PieceLength) - fmt.Printf("%+v\n", len(metaInfo.Info.Pieces)) - fmt.Printf("%+v\n", hex.EncodeToString(metaInfo.infoHash[:])) -} diff --git a/pkg/protocol/bt/peer/handshake.go b/pkg/protocol/bt/peer/handshake.go deleted file mode 100644 index 5e30913b..00000000 --- a/pkg/protocol/bt/peer/handshake.go +++ /dev/null @@ -1,51 +0,0 @@ -package peer - -import ( - "bytes" - "encoding/binary" -) - -const ( - ProtocolIdentifier = "BitTorrent protocol" - ProtocolIdentifierLength = 0x13 -) - -// Handshake of Peer wire protocol -// Per https://wiki.theory.org/index.php/BitTorrentSpecification#Handshake -type Handshake struct { - Pstrlen byte - Pstr [19]byte - Reserved [8]byte - InfoHash [20]byte - PeerID [20]byte -} - -func NewHandshake(reserved [8]byte, infoHash [20]byte, peerID [20]byte) *Handshake { - var arr [ProtocolIdentifierLength]byte - copy(arr[:], ProtocolIdentifier) - return &Handshake{ - Pstrlen: ProtocolIdentifierLength, - Pstr: arr, - Reserved: reserved, - InfoHash: infoHash, - PeerID: peerID, - } -} - -func (handshake *Handshake) Encode() []byte { - writer := new(bytes.Buffer) - err := binary.Write(writer, binary.BigEndian, handshake) - if err != nil { - panic(err) - } - return writer.Bytes() -} - -func (handshake *Handshake) Decode(buf []byte) error { - reader := bytes.NewReader(buf) - err := binary.Read(reader, binary.BigEndian, handshake) - if err != nil { - return err - } - return nil -} diff --git a/pkg/protocol/bt/peer/message/message.go b/pkg/protocol/bt/peer/message/message.go deleted file mode 100644 index 0fc50fab..00000000 --- a/pkg/protocol/bt/peer/message/message.go +++ /dev/null @@ -1,143 +0,0 @@ -package message - -import ( - "encoding/binary" -) - -// message ID -type ID byte - -const ( - IdChoke ID = iota - IdUnchoke - IdInterested - IdNotInterested - IdHave - IdBitfield - IdRequest - IdPiece - IdCancel -) - -/*type Message struct { - Length uint32 - ID MessageID - Payload []byte -}*/ - -type base struct { - id ID -} - -func (base *base) ID() ID { - return base.id -} - -func (base *base) Encode() []byte { - return encode(base, nil) -} - -func (base *base) Decode(body []byte) { - -} - -type Message interface { - ID() ID - Encode() []byte - Decode(body []byte) -} - -type TestMsg struct { - *base -} - -func (base *TestMsg) Encode() []byte { - return []byte{1, 2, 3} -} - -func (base *TestMsg) Decode(body []byte) { -} - -func encode(base *base, body []byte) []byte { - buf := make([]byte, 5+len(body)) - binary.BigEndian.PutUint32(buf, uint32(len(buf))) - buf[4] = byte(base.id) - copy(buf[4:], body) - return buf -} - -/*func Decode(buf []byte) Message { - head := make([]byte, 5) - body := message.Encode() - binary.BigEndian.PutUint32(head, uint32(len(head)+len(body))) - head[4] = byte(message.ID()) - return append(head, body...) -} - -func (msg *Message) ToBytes() []byte { - buf := make([]byte, 5) - binary.BigEndian.PutUint32(buf, msg.Length) - buf[4] = byte(msg.ID) - if msg.Payload != nil { - return append(buf, msg.Payload.Encode()...) - } else { - return buf - } -} - -func (msg *Message) FormBytes(buf []byte) { - msg.Length = binary.BigEndian.Uint32(buf) - msg.ID = ID(buf[4]) - if msg.Payload != nil { - msg.Payload.Decode(buf[4:]) - } -}*/ - -/*func NewMessage(id MessageID, Payload []byte) *Message { - message := &Message{ID: id, Payload: Payload} - if id == Keepalive { - message.Length = 0 - } else { - message.Length = uint32(len(Payload) + 1) - } - return message -} - -func (msg *Message) Encode() []byte { - if msg.ID == Keepalive { - return make([]byte, 4) - } else { - buf := make([]byte, 5+len(msg.Payload)) - binary.BigEndian.PutUint32(buf, msg.Length) - buf[4] = byte(msg.ID) - copy(buf[5:], msg.Payload[:]) - return buf - } -} - -func (msg *Message) Decode(buf []byte) { - msg.Length = binary.BigEndian.Uint32(buf) - if msg.Length == 0 { - msg.ID = -1 - } else { - msg.ID = MessageID(buf[4]) - if msg.Length > 4 { - msg.Payload = buf[5:] - } - } -}*/ - -// 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)) - if len(data)-4 >= length { - return length + 4, data[:length+4], nil - } - } - return -} diff --git a/pkg/protocol/bt/peer/message/msg_bitfield.go b/pkg/protocol/bt/peer/message/msg_bitfield.go deleted file mode 100644 index 73758181..00000000 --- a/pkg/protocol/bt/peer/message/msg_bitfield.go +++ /dev/null @@ -1,68 +0,0 @@ -package message - -import ( - "github.com/RoaringBitmap/roaring" - "math" -) - -// bitfield: -type Bitfield struct { - *base - pieceCount int - bitmap *roaring.Bitmap -} - -func NewBitfield() *Bitfield { - return &Bitfield{ - base: &base{ - id: IdBitfield, - }, - } -} - -func BuildBitfield(pieceCount int, bitmap *roaring.Bitmap) *Bitfield { - bitfield := NewBitfield() - bitfield.pieceCount = pieceCount - bitfield.bitmap = bitmap - return bitfield -} - -func (b *Bitfield) Encode() []byte { - buf := make([]byte, int(math.Ceil(float64(b.pieceCount)/8))) - for i := 0; i < len(buf); i++ { - temp := byte(0) - for j := 0; j < 8; j++ { - // 如果piece存在,位数标识为1 - if b.bitmap.ContainsInt(i*8 + j) { - temp = temp | (1 << (7 - j)) - } - } - buf[i] = temp - } - return encode(b.base, buf) -} - -func (b *Bitfield) Decode(body []byte) { - b.bitmap = roaring.New() - // 初始化bitmap - for i := 0; i < len(body); i++ { - for j := 0; j < 8; j++ { - if body[i]&(1<<(7-j)) > 0 { - b.bitmap.AddInt(i*8 + j) - } - } - } -} - -// 某个分片是否下载完成 -func (b *Bitfield) IsComplete(i int) bool { - return b.bitmap.ContainsInt(i) -} - -// 给定一组分片下载状态,计算出当前peer能提供下载的分片下标,如果已经拥有则不用提供 -// has = 0b01111111,had = 0b10010000 -// has & ~had = 0b01111111 & 0b01101111 = 0b01101111 -func (b *Bitfield) Provide(had *roaring.Bitmap) []uint32 { - xor := roaring.AndNot(b.bitmap, had) - return xor.ToArray() -} diff --git a/pkg/protocol/bt/peer/message/msg_bitfield_test.go b/pkg/protocol/bt/peer/message/msg_bitfield_test.go deleted file mode 100644 index 7e0e8c48..00000000 --- a/pkg/protocol/bt/peer/message/msg_bitfield_test.go +++ /dev/null @@ -1,111 +0,0 @@ -package message - -import ( - "github.com/RoaringBitmap/roaring" - "reflect" - "testing" -) - -func TestBitfield_IsComplete(t *testing.T) { - type fields struct { - payload *roaring.Bitmap - } - type args struct { - i int - } - tests := []struct { - name string - fields fields - args args - want bool - }{ - { - "case-1", - fields{ - payload: roaring.BitmapOf(1, 2, 3, 4), - }, - args{ - 1, - }, - true, - }, - { - "case-2", - fields{ - payload: roaring.BitmapOf(1, 2, 3, 4), - }, - args{ - 5, - }, - false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - b := &Bitfield{ - pieceCount: 100, - bitmap: tt.fields.payload, - } - if got := b.IsComplete(tt.args.i); got != tt.want { - t.Errorf("Bitfield.IsComplete() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestBitfield_Provide(t *testing.T) { - type fields struct { - payload *roaring.Bitmap - } - type args struct { - had *roaring.Bitmap - } - tests := []struct { - name string - fields fields - args args - want []uint32 - }{ - { - "case-1", - fields{ - payload: roaring.BitmapOf(0, 1, 2, 3, 4), - }, - args{ - roaring.BitmapOf(0), - }, - []uint32{1, 2, 3, 4}, - }, - { - "case-2", - fields{ - payload: roaring.BitmapOf(0, 1, 2, 3, 4, 5, 6, 7, 8), - }, - args{ - roaring.BitmapOf(0, 1, 3, 5, 6, 8), - }, - []uint32{2, 4, 7}, - }, - { - "case-3", - fields{ - payload: roaring.BitmapOf(), - }, - args{ - roaring.BitmapOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9), - }, - []uint32{}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - b := &Bitfield{ - pieceCount: 100, - bitmap: tt.fields.payload, - } - if got := b.Provide(tt.args.had); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Bitfield.Provide() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/pkg/protocol/bt/peer/message/msg_choke.go b/pkg/protocol/bt/peer/message/msg_choke.go deleted file mode 100644 index e7446914..00000000 --- a/pkg/protocol/bt/peer/message/msg_choke.go +++ /dev/null @@ -1,14 +0,0 @@ -package message - -// choke: -type Choke struct { - *base -} - -func NewChoke() *Choke { - return &Choke{ - base: &base{ - id: IdChoke, - }, - } -} diff --git a/pkg/protocol/bt/peer/message/msg_interested.go b/pkg/protocol/bt/peer/message/msg_interested.go deleted file mode 100644 index ab17bec7..00000000 --- a/pkg/protocol/bt/peer/message/msg_interested.go +++ /dev/null @@ -1,14 +0,0 @@ -package message - -// choke: -type Interested struct { - *base -} - -func NewInterested() *Interested { - return &Interested{ - base: &base{ - id: IdInterested, - }, - } -} diff --git a/pkg/protocol/bt/peer/message/msg_keepalive.go b/pkg/protocol/bt/peer/message/msg_keepalive.go deleted file mode 100644 index 14633c53..00000000 --- a/pkg/protocol/bt/peer/message/msg_keepalive.go +++ /dev/null @@ -1,16 +0,0 @@ -package message - -// keep-alive: -type Keepalive struct { - *base -} - -var data = make([]byte, 4) - -func (k *Keepalive) Encode() []byte { - return data -} - -func (k *Keepalive) Decode(body []byte) { - -} diff --git a/pkg/protocol/bt/peer/message/msg_not_interested.go b/pkg/protocol/bt/peer/message/msg_not_interested.go deleted file mode 100644 index cbb9eab4..00000000 --- a/pkg/protocol/bt/peer/message/msg_not_interested.go +++ /dev/null @@ -1,14 +0,0 @@ -package message - -// choke: -type NotInterested struct { - *base -} - -func NewNotInterested() *NotInterested { - return &NotInterested{ - base: &base{ - id: IdNotInterested, - }, - } -} diff --git a/pkg/protocol/bt/peer/message/msg_piece.go b/pkg/protocol/bt/peer/message/msg_piece.go deleted file mode 100644 index 182b088d..00000000 --- a/pkg/protocol/bt/peer/message/msg_piece.go +++ /dev/null @@ -1,42 +0,0 @@ -package message - -import "encoding/binary" - -// piece: -type Piece struct { - *base - Index uint32 - Begin uint32 - Block []byte -} - -func NewPiece() *Piece { - return &Piece{ - base: &base{ - id: IdPiece, - }, - } -} - -func BuildPiece(index uint32, begin uint32, block []byte) *Piece { - piece := NewPiece() - piece.Index = index - piece.Begin = begin - piece.Block = block - return piece - -} - -func (p *Piece) Encode() []byte { - buf := make([]byte, 8+len(p.Block)) - binary.BigEndian.PutUint32(buf[0:4], p.Index) - binary.BigEndian.PutUint32(buf[4:8], p.Begin) - copy(buf[8:], p.Block) - return encode(p.base, buf) -} - -func (p *Piece) Decode(buf []byte) { - p.Index = binary.BigEndian.Uint32(buf[0:4]) - p.Begin = binary.BigEndian.Uint32(buf[4:8]) - p.Block = buf[8:] -} diff --git a/pkg/protocol/bt/peer/message/msg_request.go b/pkg/protocol/bt/peer/message/msg_request.go deleted file mode 100644 index 88165921..00000000 --- a/pkg/protocol/bt/peer/message/msg_request.go +++ /dev/null @@ -1,43 +0,0 @@ -package message - -import ( - "encoding/binary" -) - -// request: -type Request struct { - *base - Index uint32 - Begin uint32 - Length uint32 -} - -func NewRequest() *Request { - return &Request{ - base: &base{ - id: IdRequest, - }, - } -} - -func BuildRequest(index uint32, begin uint32, length uint32) *Request { - request := NewRequest() - request.Index = index - request.Begin = begin - request.Length = length - return request -} - -func (r *Request) Encode() []byte { - buf := make([]byte, 12) - binary.BigEndian.PutUint32(buf[0:4], r.Index) - binary.BigEndian.PutUint32(buf[4:8], r.Begin) - binary.BigEndian.PutUint32(buf[8:12], r.Length) - return encode(r.base, buf) -} - -func (r *Request) Decode(buf []byte) { - r.Length = binary.BigEndian.Uint32(buf[0:4]) - r.Begin = binary.BigEndian.Uint32(buf[4:8]) - r.Index = binary.BigEndian.Uint32(buf[8:12]) -} diff --git a/pkg/protocol/bt/peer/message/msg_unchoke.go b/pkg/protocol/bt/peer/message/msg_unchoke.go deleted file mode 100644 index 2fd6e994..00000000 --- a/pkg/protocol/bt/peer/message/msg_unchoke.go +++ /dev/null @@ -1,14 +0,0 @@ -package message - -// unchoke: -type Unchoke struct { - *base -} - -func NewUnchoke() *Unchoke { - return &Unchoke{ - base: &base{ - id: IdUnchoke, - }, - } -} diff --git a/pkg/protocol/bt/peer/peer.go b/pkg/protocol/bt/peer/peer.go deleted file mode 100644 index f36465f0..00000000 --- a/pkg/protocol/bt/peer/peer.go +++ /dev/null @@ -1,30 +0,0 @@ -package peer - -import ( - "encoding/binary" - "math/rand" - "net" - "strconv" -) - -type Peer struct { - IP uint32 - Port uint16 -} - -func (peer *Peer) Address() string { - bts := make([]byte, 4) - binary.BigEndian.PutUint32(bts, peer.IP) - return net.IP(bts).String() + ":" + strconv.Itoa(int(peer.Port)) -} - -// 生成Peer ID,规则为前三位固定字母(-GP)+SemVer(xyz)+End(-),后面随机生成 -// 参考:https://wiki.theory.org/index.php/BitTorrentSpecification#peer_id -func GenPeerID() [20]byte { - peerID := [20]byte{'-', 'G', 'P', '0', '0', '1', '-'} - _, err := rand.Read(peerID[7:]) - if err != nil { - panic(err) - } - return peerID -} diff --git a/pkg/protocol/bt/testdata/Game.of.Thrones.S08E05.720p.WEB.H264-MEMENTO.torrent b/pkg/protocol/bt/testdata/Game.of.Thrones.S08E05.720p.WEB.H264-MEMENTO.torrent deleted file mode 100644 index bad833c442db145ffd50852177723918d36a0eef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28480 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 diff --git a/pkg/protocol/bt/testdata/office.torrent b/pkg/protocol/bt/testdata/office.torrent deleted file mode 100644 index 15768d09133047c71390ebfd61e7c26903895d23..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12575 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 diff --git a/pkg/protocol/bt/torrent/main/main.go b/pkg/protocol/bt/torrent/main/main.go deleted file mode 100644 index 7bb0e09b..00000000 --- a/pkg/protocol/bt/torrent/main/main.go +++ /dev/null @@ -1,74 +0,0 @@ -package main - -import ( - "github.com/monkeyWie/gopeed-core/pkg/protocol/bt/metainfo" - "github.com/monkeyWie/gopeed-core/pkg/protocol/bt/peer" - "github.com/monkeyWie/gopeed-core/pkg/protocol/bt/torrent" - log "github.com/sirupsen/logrus" -) - -func main() { - log.SetLevel(log.DebugLevel) - torrent := buildTorrent() - torrent.Download("e:/testbt/download") -} - -func buildTorrent() *torrent.Torrent { - // metaInfo, err := metainfo.ParseFromFile("../testdata/Game.of.Thrones.S08E05.720p.WEB.H264-MEMENTO.torrent") - metaInfo, err := metainfo.ParseFromFile("../../testdata/office.torrent") - if err != nil { - panic(err) - } - - metaInfo.AnnounceList[0] = append(metaInfo.AnnounceList[0], []string{ - "udp://tracker.coppersurfer.tk:6969/announce", - "udp://tracker.leechers-paradise.org:6969/announce", - "udp://tracker.opentrackr.org:1337/announce", - "udp://p4p.arenabg.com:1337/announce", - "udp://9.rarbg.to:2710/announce", - "udp://9.rarbg.me:2710/announce", - "udp://tracker.internetwarriors.net:1337/announce", - "udp://exodus.desync.com:6969/announce", - "udp://tracker.tiny-vps.com:6969/announce", - "udp://tracker.sbsub.com:2710/announce", - "udp://retracker.lanta-net.ru:2710/announce", - "udp://open.stealth.si:80/announce", - "udp://open.demonii.si:1337/announce", - "udp://tracker.torrent.eu.org:451/announce", - "udp://tracker.moeking.me:6969/announce", - "udp://tracker.cyberia.is:6969/announce", - "udp://denis.stalker.upeer.me:6969/announce", - "udp://ipv4.tracker.harry.lu:80/announce", - "udp://explodie.org:6969/announce", - "udp://zephir.monocul.us:6969/announce", - "udp://xxxtor.com:2710/announce", - "udp://valakas.rollo.dnsabr.com:2710/announce", - "udp://tracker.zerobytes.xyz:1337/announce", - "udp://tracker.yoshi210.com:6969/announce", - "udp://tracker.uw0.xyz:6969/announce", - "udp://tracker.swateam.org.uk:2710/announce", - "udp://tracker.nyaa.uk:6969/announce", - "udp://tracker.filemail.com:6969/announce", - "udp://tracker.ds.is:6969/announce", - "udp://retracker.sevstar.net:2710/announce", - "udp://retracker.netbynet.ru:2710/announce", - "udp://retracker.akado-ural.ru:80/announce", - "udp://opentracker.i2p.rocks:6969/announce", - "udp://opentor.org:2710/announce", - "udp://open.nyap2p.com:6969/announce", - "udp://chihaya.toss.li:9696/announce", - "udp://bt2.archive.org:6969/announce", - "udp://bt1.archive.org:6969/announce", - "udp://www.loushao.net:8080/announce", - "udp://tracker4.itzmx.com:2710/announce", - "udp://tracker3.itzmx.com:6961/announce", - "udp://tracker2.itzmx.com:6961/announce", - "udp://tracker.lelux.fi:6969/announce", - "udp://tracker.kamigami.org:2710/announce", - "udp://tracker.dler.org:6969/announce", - "udp://tr.bangumi.moe:6969/announce", - "udp://qg.lorzl.gq:2710/announce", - "udp://bt2.54new.com:8080/announce", - }...) - return torrent.NewTorrent(peer.GenPeerID(), metaInfo) -} diff --git a/pkg/protocol/bt/torrent/peer_conn.go b/pkg/protocol/bt/torrent/peer_conn.go deleted file mode 100644 index e90a574a..00000000 --- a/pkg/protocol/bt/torrent/peer_conn.go +++ /dev/null @@ -1,464 +0,0 @@ -package torrent - -import ( - "bufio" - "crypto/sha1" - "encoding/binary" - "errors" - "fmt" - "github.com/RoaringBitmap/roaring" - "io" - "net" - "os" - "path" - "path/filepath" - "time" - - "github.com/cenkalti/mse" - "github.com/monkeyWie/gopeed-core/pkg/protocol/bt/metainfo" - "github.com/monkeyWie/gopeed-core/pkg/protocol/bt/peer" - "github.com/monkeyWie/gopeed-core/pkg/protocol/bt/peer/message" - log "github.com/sirupsen/logrus" -) - -// 现在主流客户端的block大小都是16KB -const blockSize = 2 << 13 -const keepaliveTimeout = 60 * 2 - -var errPieceCheckFailed = errors.New("piece check failed") -var keepaliveData = make([]byte, 4) - -type peerConn struct { - torrent *Torrent - peer *peer.Peer - 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 - - // 上一次收到包的时间 - lastReciveTime int64 - - bitfield *message.Bitfield - readyEnd bool - - downloadedCh chan error - disconnectCh chan error - // block下载队列,官方推荐为5 - blockQueueCh chan interface{} -} - -func newPeerConn(torrent *Torrent, peer *peer.Peer) *peerConn { - return &peerConn{ - torrent: torrent, - peer: peer, - } -} - -// 使用MSE加密来避免运营商对bt流量的封锁,基本上现在市面上BT客户端都默认开启了,不用MSE的话很多Peer拒绝连接 -// see http://wiki.vuze.com/w/Message_Stream_Encryption -func (pc *peerConn) dialMse() error { - conn, err := net.DialTimeout("tcp", pc.peer.Address(), time.Second*time.Duration(30)) - if err != nil { - return err - } - mseConn := mse.WrapConn(conn) - infoHash := pc.torrent.MetaInfo.GetInfoHash() - _, err = mseConn.HandshakeOutgoing(infoHash[:], mse.PlainText, nil) - if err != nil { - mseConn.Close() - return err - } - pc.conn = mseConn - return nil -} - -// Handshake of Peer wire protocol -// see https://wiki.theory.org/index.php/BitTorrentSpecification#Handshake -func (pc *peerConn) handshake() (*peer.Handshake, error) { - handshakeRes, err := func() (*peer.Handshake, error) { - handshakeReq := peer.NewHandshake([8]byte{}, pc.torrent.MetaInfo.GetInfoHash(), pc.torrent.PeerID) - _, err := pc.conn.Write(handshakeReq.Encode()) - if err != nil { - return nil, err - } - var read [68]byte - _, err = io.ReadFull(pc.conn, read[:]) - if err != nil { - return nil, err - } - handshakeRes := &peer.Handshake{} - err = handshakeRes.Decode(read[:]) - if err != nil { - return nil, err - } - // InfoHash不匹配 - if handshakeRes.InfoHash != handshakeReq.InfoHash { - return nil, fmt.Errorf("info_hash not currently serving") - } - return handshakeRes, nil - }() - if err != nil { - pc.conn.Close() - return nil, err - } - // init state - pc.amChoking = true - pc.amInterested = false - pc.peerChoking = true - pc.peerInterested = false - return handshakeRes, nil -} - -func (pc *peerConn) handleKeepalive() { - pc.lastReciveTime = time.Now().Unix() - for { - time.Sleep(time.Minute * 2) - // 如果超过两分钟没有响应,则断开连接 - if time.Now().Unix()-pc.lastReciveTime > keepaliveTimeout { - pc.conn.Close() - break - } - // 两分钟发送一次心跳包 - _, err := pc.conn.Write(keepaliveData) - if err != nil { - pc.conn.Close() - break - } - } -} - -// 准备下载 -func (pc *peerConn) ready() error { - if err := pc.dialMse(); err != nil { - return fmt.Errorf("tcp dial error %w", err) - } - if _, err := pc.handshake(); err != nil { - return fmt.Errorf("handshake error %w", err) - } - pc.readyEnd = false - readyCh := make(chan bool) - pc.disconnectCh = make(chan error) - go pc.handleKeepalive() - go func() { - scanner := bufio.NewScanner(pc.conn) - scanner.Split(message.SplitMessage) - for scanner.Scan() { - pc.lastReciveTime = time.Now().Unix() - buf := scanner.Bytes() - length := binary.BigEndian.Uint32(buf[:4]) - if length == 0 { - // keepalive - } else { - switch message.ID(buf[4]) { - case message.IdChoke: - break - case message.IdUnchoke: - pc.handleUnchoke(readyCh) - break - case message.IdInterested: - break - case message.IdNotInterested: - break - case message.IdHave: - break - case message.IdBitfield: - pc.handleBitfield(buf) - break - case message.IdRequest: - break - case message.IdPiece: - pc.handlePiece(buf) - break - case message.IdCancel: - break - } - } - } - // 还未下载完成时连接断开 - err := scanner.Err() - if err == nil { - err = io.EOF - } - pc.disconnectCh <- err - close(pc.disconnectCh) - }() - err := func() error { - select { - case status := <-readyCh: - if status { - return nil - } - return errors.New("ready fail") - case <-time.After(time.Second * 30): - // 30秒之后超时 - return errors.New("ready time out") - } - }() - if err != nil { - pc.conn.Close() - return err - } - return nil -} - -// 下载指定piece -func (pc *peerConn) downloadPiece(index int) (err error) { - pieceLength := pc.torrent.MetaInfo.GetPieceLength(index) - pc.downloadedCh = make(chan error) - pc.blockQueueCh = make(chan interface{}, 5) - defer close(pc.blockQueueCh) - // 按块下载分片 - blockCount := pc.torrent.pieceStates.states[index].blockCount - for i := 0; i < blockCount; i++ { - offset := blockSize * i - // 如果已经下载过就跳过 - if pc.torrent.pieceStates.isBlockDone(index, offset) { - continue - } - - var blockLength uint32 - if i == blockCount-1 { - // 最后一个block大小需要计算出来 - blockLength = uint32(pieceLength - offset) - } else { - blockLength = blockSize - } - // block下载排队 - select { - case pc.blockQueueCh <- nil: - break - case err = <-pc.downloadedCh: - break - case err = <-pc.disconnectCh: - break - } - // 如果连接出现问题或下载失败直接返回异常 - if err != nil { - pc.conn.Close() - return - } - // 发起request,对方会响应piece - _, err = pc.conn.Write(message.BuildRequest(uint32(index), uint32(offset), blockLength).Encode()) - if err != nil { - break - } - } - select { - case err = <-pc.downloadedCh: - break - case err = <-pc.disconnectCh: - break - } - if err != nil { - pc.conn.Close() - } - return -} - -func (pc *peerConn) handleUnchoke(readyCh chan<- bool) { - pc.peerChoking = false - // 已经处理过Unchoke信号 - if pc.readyEnd { - return - } - pc.readyEnd = true - // 如果客户端对peer感兴趣并且peer没有choked客户端,就可以开始下载了 - if pc.amInterested { - readyCh <- true - } else { - readyCh <- false - } - close(readyCh) -} - -func (pc *peerConn) handleBitfield(buf []byte) { - pc.bitfield = message.NewBitfield() - pc.bitfield.Decode(buf) - have := pc.getHavePieces(pc.bitfield) - if len(have) > 0 { - // 表示对该peer感兴趣,并且不choked该peer - pc.conn.Write(message.NewInterested().Encode()) - pc.amInterested = true - - pc.conn.Write(message.NewUnchoke().Encode()) - pc.amChoking = false - } else { - pc.conn.Close() - } -} - -// 处理下载响应,每次接收到响应直接将block写入到对应文件中 -func (pc *peerConn) handlePiece(buf []byte) { - piece := message.NewPiece() - piece.Decode(buf) - info := pc.torrent.MetaInfo.Info - fds := pc.torrent.MetaInfo.GetFileDetails() - blockLength := int64(len(piece.Block)) - pieceBegin := int64(piece.Index) * int64(info.PieceLength) - blockBegin := pieceBegin + int64(piece.Begin) - // 计算block对应要写到的文件偏移 - var fileBlocks []*fileBlock - if len(info.Files) == 0 { - // 单文件 - fileBlocks = append(fileBlocks, &fileBlock{ - filepath: info.Name, - fileSeek: blockBegin, - blockBegin: 0, - blockEnd: blockLength, - }) - } else { - // 获取要写入的第一个文件和偏移 - writeIndex := getWriteFile(blockBegin, fds) - // block对应种子的偏移 - blockFileBegin := blockBegin - // block对应文件的偏移 - var blockSeek int64 = 0 - for _, f := range fds[writeIndex:] { - // 计算文件可剩余写入字节数 - fileWritable := f.End - blockFileBegin - // 计算block剩余写入字节数 - blockWritable := blockLength - blockSeek - fb := &fileBlock{ - filepath: filepath.Join(f.Path...), - fileSeek: blockFileBegin - f.Begin, - blockBegin: blockSeek, - } - if fileWritable >= blockWritable { - // 若够block写入直接跳出循环 - fb.blockEnd = blockSeek + blockWritable - fileBlocks = append(fileBlocks, fb) - break - } else { - // 否则计算剩余可写字节数,写入到下一个文件中 - fb.blockEnd = blockSeek + fileWritable - fileBlocks = append(fileBlocks, fb) - blockFileBegin = f.End - blockSeek += fileWritable - } - } - } - // 开始写入文件 - for _, f := range fileBlocks { - err := func() error { - name := filepath.Join(pc.torrent.Path, f.filepath) - file, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE, 0644) - if err != nil { - return err - } - defer file.Close() - _, err = file.WriteAt(piece.Block[f.blockBegin:f.blockEnd], f.fileSeek) - if err != nil { - return err - } - return nil - }() - if err != nil { - pc.downloadedCh <- errPieceCheckFailed - pc.conn.Close() - return - } - } - pc.torrent.pieceStates.setBlockDone(int(piece.Index), int(piece.Begin)) - // 出队 - <-pc.blockQueueCh - - log.Debugf("piece:%d block:%d", piece.Index, piece.Begin) - - // piece全部下载完 - if pc.torrent.pieceStates.isPieceDone(int(piece.Index)) { - // 计算piece对应的文件偏移 - sha1 := sha1.New() - pieceLength := pc.torrent.MetaInfo.GetPieceLength(int(piece.Index)) - writeIndex := getWriteFile(pieceBegin, fds) - fileBegin := pieceBegin - fds[writeIndex].Begin - for _, fd := range fds[writeIndex:] { - func() { - file, err := os.Open(path.Join(pc.torrent.Path, fd.Path[0])) - if err != nil { - panic(err) - } - defer file.Close() - _, err = file.Seek(fileBegin, 0) - if err != nil { - panic(err) - } - written, err := io.CopyN(sha1, file, int64(pieceLength)) - if err != nil { - if err != io.EOF { - panic(err) - } - } - pieceLength -= int(written) - }() - if pieceLength > 0 { - // 继续读下个文件 - fileBegin = 0 - } else { - break - } - } - // 断开连接 TODO 用连接池进行复用 - pc.conn.Close() - - // 校验piece SHA-1 hash - downHash := [20]byte{} - copy(downHash[:], sha1.Sum(nil)) - if downHash == pc.torrent.MetaInfo.Info.Pieces[piece.Index] { - // piece下载完成 - pc.downloadedCh <- nil - } else { - pc.downloadedCh <- errPieceCheckFailed - } - close(pc.downloadedCh) - } -} - -// 获取piece对应要写入的文件 -// TODO 使用sort.Search二分查找法检索 -func getWriteFile(pieceBegin int64, fds []*metainfo.FileDetail) int { - for i, f := range fds { - if f.Begin <= pieceBegin && f.End > pieceBegin { - return i - } - } - return -1 -} - -type fileBlock struct { - filepath string - fileSeek int64 - blockBegin int64 - blockEnd int64 -} - -// 获取peer能提供需要下载的文件分片 -func (pc *peerConn) getHavePieces(bitfield *message.Bitfield) []uint32 { - had := roaring.New() - for i := 0; i < pc.torrent.pieceStates.size(); i++ { - if pc.torrent.pieceStates.getState(i) == stateFinish { - had.AddInt(i) - } - } - return bitfield.Provide(had) -} - -// 获取要写入到的文件 -/*func (ps *peerConn) getWriteFile(request *message.Request) string { - info := ps.torrent.MetaInfo.Info - // 单文件 - if len(info.Files) == 0 { - return info.Name - } else { - request.Index * info.PieceLength + be - for i := 0; i < len(info.Files); i++ { - - } - } -} -*/ diff --git a/pkg/protocol/bt/torrent/peer_conn_test.go b/pkg/protocol/bt/torrent/peer_conn_test.go deleted file mode 100644 index 07e38bd2..00000000 --- a/pkg/protocol/bt/torrent/peer_conn_test.go +++ /dev/null @@ -1,122 +0,0 @@ -package torrent - -import ( - "fmt" - "github.com/monkeyWie/gopeed-core/pkg/protocol/bt/peer" - "github.com/monkeyWie/gopeed-core/pkg/protocol/bt/tracker" - "os" - "sync" - "testing" - "time" -) - -func Test_peerState_ready(t *testing.T) { - - ps := getUsablePeer() - fmt.Println("get a peer") - - // 下载前准备 - err := ps.ready() - fmt.Println("ready end") - if err != nil { - fmt.Println(ps.peer.Address()) - t.Fatal(err) - } - -} - -func Test_peerState_downloadPiece(t *testing.T) { - - ps := getUsablePeer() - fmt.Println("get a peer") - - // 下载前准备 - err := ps.ready() - if err != nil { - t.Fatal(err) - } - have := ps.getHavePieces(ps.bitfield) - fmt.Printf("download is ready,have %d\n", len(have)) - if len(have) > 0 { - // 获取分片的长度 - for _, index := range have { - fmt.Printf("download index %d\n", index) - ps.downloadPiece(int(index)) - } - return - } -} - -// 获取一个能连接上的peer -func getUsablePeer() *peerConn { - ch := getUsablePeerMore() - ps := <-ch - return ps -} - -// 获取多个能连接上的peer -func getUsablePeerMore() chan *peerConn { - torrent := buildTorrent() - tracker := &tracker.Tracker{ - PeerID: torrent.PeerID, - MetaInfo: torrent.MetaInfo, - } - peers := <-tracker.Tracker() - ch := make(chan *peerConn) - for i := range peers { - go peerTest(torrent, peers[i], ch) - } - return ch -} - -func peerTest(torrent *Torrent, peer *peer.Peer, ch chan *peerConn) { - ps := &peerConn{ - torrent: torrent, - peer: peer, - } - // 连接至peer - err := ps.ready() - if err != nil { - return - } - ps.conn.Close() - ch <- ps -} - -func TestSome(t *testing.T) { - ch := make(chan interface{}) - - go func() { - time.Sleep(time.Second) - select { - case ch <- nil: - fmt.Println("write") - case <-time.After(time.Second * 3): - fmt.Println("timeout") - } - }() - - time.Sleep(time.Second * 5) - r := <-ch - fmt.Println(r) - fmt.Println("111") - -} - -func TestSome2(t *testing.T) { - wg := sync.WaitGroup{} - wg.Add(10) - file, err := os.OpenFile("e:/testbt/test.data", os.O_RDWR|os.O_CREATE, 0644) - for i := 0; i < 10; i++ { - go func(i int) { - for j := 0; j < 100; j++ { - if err != nil { - panic(err) - } - _, err = file.WriteAt([]byte{byte(i)}, int64(i*8)) - } - wg.Done() - }(i) - } - wg.Wait() -} diff --git a/pkg/protocol/bt/torrent/peer_pool.go b/pkg/protocol/bt/torrent/peer_pool.go deleted file mode 100644 index c126d8d4..00000000 --- a/pkg/protocol/bt/torrent/peer_pool.go +++ /dev/null @@ -1,99 +0,0 @@ -package torrent - -import ( - "sync" - "time" - - "github.com/monkeyWie/gopeed-core/pkg/protocol/bt/peer" - "github.com/monkeyWie/gopeed-core/pkg/protocol/bt/tracker" -) - -type peerPool struct { - torrent *Torrent - - states map[*peer.Peer]*peerState - lock *sync.Mutex -} - -type peerState struct { - // 是否在被使用 - using bool - // 下载失败次数 - errors int -} - -func newPeerPool(torrent *Torrent) *peerPool { - return &peerPool{ - torrent: torrent, - lock: &sync.Mutex{}, - states: map[*peer.Peer]*peerState{}, - } -} - -func (pp *peerPool) fetch() { - tracker := &tracker.Tracker{ - PeerID: pp.torrent.PeerID, - MetaInfo: pp.torrent.MetaInfo, - } - - go func() { - for { - // 当peer数量少于200个时重新发起一次tracker - if len(pp.states) < 200 { - go func() { - for peers := range tracker.Tracker() { - pp.put(peers) - } - }() - } - // 每2分钟检测一次 - time.Sleep(time.Minute * 2) - } - }() -} - -func (pp *peerPool) put(peers []*peer.Peer) { - pp.lock.Lock() - defer pp.lock.Unlock() - for _, peer := range peers { - if _, ok := pp.states[peer]; !ok { - pp.states[peer] = &peerState{ - using: false, - errors: 0, - } - } - - } -} - -func (pp *peerPool) get() *peer.Peer { - pp.lock.Lock() - defer pp.lock.Unlock() - for peer, state := range pp.states { - if !state.using { - state.using = true - return peer - } - } - return nil -} - -// 将peer重新放回池中,等待使用 -func (pp *peerPool) release(peer *peer.Peer) { - pp.lock.Lock() - defer pp.lock.Unlock() - pp.states[peer].using = false - pp.states[peer].errors = 0 -} - -// 标记peer为不可用,超过3次则剔除该peer -func (pp *peerPool) unavailable(peer *peer.Peer) { - pp.lock.Lock() - defer pp.lock.Unlock() - pp.states[peer].using = false - pp.states[peer].errors++ - if pp.states[peer].errors > 3 { - delete(pp.states, peer) - } - -} diff --git a/pkg/protocol/bt/torrent/piece_state.go b/pkg/protocol/bt/torrent/piece_state.go deleted file mode 100644 index 82133735..00000000 --- a/pkg/protocol/bt/torrent/piece_state.go +++ /dev/null @@ -1,109 +0,0 @@ -package torrent - -import ( - "github.com/monkeyWie/gopeed-core/pkg/protocol/bt/metainfo" - "math" - "sync" - - "github.com/RoaringBitmap/roaring" -) - -type state int - -const ( - stateReady = iota - stateDownloading - stateFinish -) - -// 所有piece的下载状态 -type pieceStates struct { - lock *sync.RWMutex - // 所有piece的block下载进度 - states map[int]*pieceState -} - -type pieceState struct { - state state - // piece中block的个数 - blockCount int - // piece中block的下载记录 - blockRecord *roaring.Bitmap -} - -func newPiecesState(meta *metainfo.MetaInfo) *pieceStates { - pieceCount := len(meta.Info.Pieces) - ps := &pieceStates{ - lock: &sync.RWMutex{}, - states: make(map[int]*pieceState, pieceCount), - } - for i := 0; i < pieceCount; i++ { - ps.states[i] = &pieceState{ - state: stateReady, - blockCount: int(math.Ceil(float64(meta.GetPieceLength(i)) / blockSize)), - blockRecord: roaring.New(), - } - } - return ps -} - -func (ps *pieceStates) getState(index int) state { - ps.lock.RLock() - defer ps.lock.RUnlock() - return ps.states[index].state -} - -func (ps *pieceStates) setState(index int, state state) { - ps.lock.Lock() - defer ps.lock.Unlock() - ps.states[index].state = state -} - -func (ps *pieceStates) size() int { - return len(ps.states) -} - -func (ps *pieceStates) getLeft() int { - ps.lock.RLock() - defer ps.lock.RUnlock() - count := 0 - for _, v := range ps.states { - if v.state == stateReady || v.state == stateDownloading { - count++ - } - } - return count -} - -func (ps *pieceStates) isDone() bool { - ps.lock.RLock() - defer ps.lock.RUnlock() - for _, v := range ps.states { - if v.state == stateReady || v.state == stateDownloading { - return false - } - } - return true -} - -func (ps *pieceStates) isPieceDone(index int) bool { - ps.lock.RLock() - defer ps.lock.RUnlock() - state := ps.states[index] - return uint64(state.blockCount) == state.blockRecord.GetCardinality() -} - -// 指定piece下的某个block是否下载完成 -func (ps *pieceStates) isBlockDone(pieceIndex int, blockIndex int) bool { - return ps.states[pieceIndex].blockRecord.ContainsInt(blockIndex) -} - -// 指定piece下的某个block下载完成 -func (ps *pieceStates) setBlockDone(pieceIndex int, blockIndex int) { - ps.states[pieceIndex].blockRecord.AddInt(blockIndex) -} - -// 清空指定piece下所有的block下载记录 -func (ps *pieceStates) clearBlocks(index int) { - ps.states[index].blockRecord.Clear() -} diff --git a/pkg/protocol/bt/torrent/torrent.go b/pkg/protocol/bt/torrent/torrent.go deleted file mode 100644 index f11c3a14..00000000 --- a/pkg/protocol/bt/torrent/torrent.go +++ /dev/null @@ -1,83 +0,0 @@ -package torrent - -import ( - "errors" - "time" - - "github.com/monkeyWie/gopeed-core/pkg/protocol/bt/metainfo" - log "github.com/sirupsen/logrus" -) - -type Torrent struct { - PeerID [20]byte - MetaInfo *metainfo.MetaInfo - - pieceStates *pieceStates - peerPool *peerPool - Path string -} - -func NewTorrent(peerID [20]byte, metaInfo *metainfo.MetaInfo) *Torrent { - torrent := &Torrent{ - PeerID: peerID, - MetaInfo: metaInfo, - pieceStates: newPiecesState(metaInfo), - } - return torrent -} - -func (t *Torrent) Download(path string) { - t.Path = path - t.peerPool = newPeerPool(t) - t.peerPool.fetch() - - pieceQueueCh := make(chan interface{}, 128) - defer close(pieceQueueCh) - downloadedCh := make(chan interface{}) - defer close(downloadedCh) - - for i := 0; i < len(t.MetaInfo.Info.Pieces); i++ { - // 加入下载队列 - pieceQueueCh <- nil - go func(index int) { - t.pieceStates.setState(index, stateDownloading) - // 一直下载到成功 - for { - peer := t.peerPool.get() - if peer == nil { - time.Sleep(time.Second * 10) - continue - } - pc := newPeerConn(t, peer) - err := pc.ready() - if err != nil { - t.peerPool.unavailable(peer) - } else { - err = pc.downloadPiece(index) - if err != nil { - log.Debugf("piece %d download fail:%s", index, err.Error()) - // 下载失败 - t.peerPool.unavailable(peer) - if errors.Is(err, errPieceCheckFailed) { - // piece校验失败,需要重新下载过一遍,清空block记录 - t.pieceStates.clearBlocks(index) - } - continue - } else { - // 下载完成 - t.pieceStates.setState(index, stateFinish) - t.peerPool.release(peer) - log.Debugf("piece %d download success,total:%d,left:%d", index, t.pieceStates.size(), t.pieceStates.getLeft()) - if t.pieceStates.isDone() { - downloadedCh <- nil - } - break - } - } - } - <-pieceQueueCh - }(i) - } - <-downloadedCh - log.Debug("download end") -} diff --git a/pkg/protocol/bt/torrent/torrent_test.go b/pkg/protocol/bt/torrent/torrent_test.go deleted file mode 100644 index 2690ce33..00000000 --- a/pkg/protocol/bt/torrent/torrent_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package torrent - -import ( - "github.com/monkeyWie/gopeed-core/pkg/protocol/bt/metainfo" - "github.com/monkeyWie/gopeed-core/pkg/protocol/bt/peer" - log "github.com/sirupsen/logrus" - "testing" -) - -func init() { - log.SetLevel(log.DebugLevel) -} - -func TestTorrent_Download(t *testing.T) { - torrent := buildTorrent() - torrent.Download("e:/testbt/download") -} - -func buildTorrent() *Torrent { - // metaInfo, err := metainfo.ParseFromFile("../testdata/Game.of.Thrones.S08E05.720p.WEB.H264-MEMENTO.torrent") - metaInfo, err := metainfo.ParseFromFile("../testdata/office.torrent") - if err != nil { - panic(err) - } - - metaInfo.AnnounceList[0] = append(metaInfo.AnnounceList[0], []string{ - "udp://tracker.coppersurfer.tk:6969/announce", - "udp://tracker.leechers-paradise.org:6969/announce", - "udp://tracker.opentrackr.org:1337/announce", - "udp://p4p.arenabg.com:1337/announce", - "udp://9.rarbg.to:2710/announce", - "udp://9.rarbg.me:2710/announce", - "udp://tracker.internetwarriors.net:1337/announce", - "udp://exodus.desync.com:6969/announce", - "udp://tracker.tiny-vps.com:6969/announce", - "udp://tracker.sbsub.com:2710/announce", - "udp://retracker.lanta-net.ru:2710/announce", - "udp://open.stealth.si:80/announce", - "udp://open.demonii.si:1337/announce", - "udp://tracker.torrent.eu.org:451/announce", - "udp://tracker.moeking.me:6969/announce", - "udp://tracker.cyberia.is:6969/announce", - "udp://denis.stalker.upeer.me:6969/announce", - "udp://ipv4.tracker.harry.lu:80/announce", - "udp://explodie.org:6969/announce", - "udp://zephir.monocul.us:6969/announce", - "udp://xxxtor.com:2710/announce", - "udp://valakas.rollo.dnsabr.com:2710/announce", - "udp://tracker.zerobytes.xyz:1337/announce", - "udp://tracker.yoshi210.com:6969/announce", - "udp://tracker.uw0.xyz:6969/announce", - "udp://tracker.swateam.org.uk:2710/announce", - "udp://tracker.nyaa.uk:6969/announce", - "udp://tracker.filemail.com:6969/announce", - "udp://tracker.ds.is:6969/announce", - "udp://retracker.sevstar.net:2710/announce", - "udp://retracker.netbynet.ru:2710/announce", - "udp://retracker.akado-ural.ru:80/announce", - "udp://opentracker.i2p.rocks:6969/announce", - "udp://opentor.org:2710/announce", - "udp://open.nyap2p.com:6969/announce", - "udp://chihaya.toss.li:9696/announce", - "udp://bt2.archive.org:6969/announce", - "udp://bt1.archive.org:6969/announce", - "udp://www.loushao.net:8080/announce", - "udp://tracker4.itzmx.com:2710/announce", - "udp://tracker3.itzmx.com:6961/announce", - "udp://tracker2.itzmx.com:6961/announce", - "udp://tracker.lelux.fi:6969/announce", - "udp://tracker.kamigami.org:2710/announce", - "udp://tracker.dler.org:6969/announce", - "udp://tr.bangumi.moe:6969/announce", - "udp://qg.lorzl.gq:2710/announce", - "udp://bt2.54new.com:8080/announce", - }...) - return NewTorrent(peer.GenPeerID(), metaInfo) -} diff --git a/pkg/protocol/bt/tracker/errors.go b/pkg/protocol/bt/tracker/errors.go deleted file mode 100644 index 8d77aa4d..00000000 --- a/pkg/protocol/bt/tracker/errors.go +++ /dev/null @@ -1,29 +0,0 @@ -package tracker - -import ( - "fmt" -) - -type ErrorCode int - -const ( - ErrTimeout ErrorCode = -1 - ErrResponse ErrorCode = -2 -) - -type BtError interface { - error -} - -type TrackerError struct { - Code ErrorCode - Err error -} - -func (te *TrackerError) Error() string { - return fmt.Sprintf("%s:%d", te.Err.Error(), te.Code) -} - -func NewTrackerError(code ErrorCode, err error) *TrackerError { - return &TrackerError{code, err} -} diff --git a/pkg/protocol/bt/tracker/tracker.go b/pkg/protocol/bt/tracker/tracker.go deleted file mode 100644 index 048f832a..00000000 --- a/pkg/protocol/bt/tracker/tracker.go +++ /dev/null @@ -1,412 +0,0 @@ -package tracker - -import ( - "encoding/binary" - "errors" - "fmt" - "io" - "io/ioutil" - "math" - "math/rand" - "net" - "net/http" - "net/url" - "strconv" - "sync" - "time" - - "github.com/monkeyWie/gopeed-core/pkg/protocol/bt/metainfo" - "github.com/monkeyWie/gopeed-core/pkg/protocol/bt/peer" -) - -const ( - udpConnectRequestMagic = 0x41727101980 - udpConnectTimeout = 15 - udpConnectRetries = 3 - udpActionConnect = 0 - udpActionAnnounce = 1 - udpActionScrape = 2 - udpActionError = 3 -) - -/* - Offset size Name Value - 0 64-bit integer protocol_id 0x41727101980 // magic constant - 8 32-bit integer action 0 // connect - 12 32-bit integer transaction_id - 16 - Per https://www.libtorrent.org/udp_tracker_protocol.html#connecting -*/ -type udpConnectRequest struct { - protocolId uint64 - action uint32 - transactionId uint32 -} - -func newUdpConnectRequest() *udpConnectRequest { - return &udpConnectRequest{ - protocolId: udpConnectRequestMagic, - action: udpActionConnect, - transactionId: rand.Uint32(), - } -} - -func (req *udpConnectRequest) encode() []byte { - buf := make([]byte, 16) - binary.BigEndian.PutUint64(buf[:8], req.protocolId) - binary.BigEndian.PutUint32(buf[8:12], req.action) - binary.BigEndian.PutUint32(buf[12:], req.transactionId) - return buf -} - -/* - Offset size Name Value - 0 32-bit integer action 0 // connect - 4 32-bit integer transaction_id - 8 64-bit integer connection_id - 16 -*/ -type udpConnectResponse struct { - action uint32 - transactionId uint32 - connectionId uint64 -} - -func newUdpConnectResponse(buf []byte) *udpConnectResponse { - return &udpConnectResponse{ - action: binary.BigEndian.Uint32(buf[:4]), - transactionId: binary.BigEndian.Uint32(buf[4:8]), - connectionId: binary.BigEndian.Uint64(buf[8:]), - } -} - -/* - Offset size Name Value - 0 64-bit integer connection_id - 8 32-bit integer action 1 // announce - 12 32-bit integer transaction_id - 16 20-byte string info_hash - 36 20-byte string peer_id - 56 64-bit integer downloaded - 64 64-bit integer left - 72 64-bit integer uploaded - 80 32-bit integer event 0 // 0: none; 1: completed; 2: started; 3: stopped - 84 32-bit integer ip address 0 // default - 88 32-bit integer key - 92 32-bit integer num_want -1 // default - 96 16-bit integer port - 98 - Per https://www.libtorrent.org/udp_tracker_protocol.html#announcing -*/ -type udpAnnounceRequest struct { - connectionId uint64 - action uint32 - transactionId uint32 - infoHash [20]byte - peerID [20]byte - downloaded uint64 - left uint64 - uploaded uint64 - event uint32 - ip uint32 - key uint32 - numWant uint32 - port uint16 -} - -func newUdpAnnounceRequest(connectionId uint64) *udpAnnounceRequest { - return &udpAnnounceRequest{ - connectionId: connectionId, - action: udpActionAnnounce, - transactionId: rand.Uint32(), - } -} - -func (req *udpAnnounceRequest) encode() []byte { - buf := make([]byte, 98, 98) - binary.BigEndian.PutUint64(buf[:8], req.connectionId) - binary.BigEndian.PutUint32(buf[8:12], req.action) - binary.BigEndian.PutUint32(buf[12:16], req.transactionId) - copy(buf[16:36], req.infoHash[:]) - copy(buf[36:56], req.peerID[:]) - binary.BigEndian.PutUint64(buf[56:64], req.downloaded) - binary.BigEndian.PutUint64(buf[64:72], req.left) - binary.BigEndian.PutUint64(buf[72:80], req.uploaded) - binary.BigEndian.PutUint32(buf[80:84], req.event) - binary.BigEndian.PutUint32(buf[84:88], req.ip) - binary.BigEndian.PutUint32(buf[88:92], req.key) - binary.BigEndian.PutUint32(buf[92:96], req.numWant) - binary.BigEndian.PutUint16(buf[96:98], req.port) - return buf -} - -/* - Offset size Name Value - 0 32-bit integer action 1 // announce - 4 32-bit integer transaction_id - 8 32-bit integer interval - 12 32-bit integer leechers - 16 32-bit integer seeders - 20 + 6 * n 32-bit integer ip address - 24 + 6 * n 16-bit integer TCP port - 20 + 6 * N - Per https://www.libtorrent.org/udp_tracker_protocol.html#announcing -*/ -type udpAnnounceResponse struct { - action uint32 - transactionId uint32 - interval uint32 - leechers uint32 - seeders uint32 - peers []*peer.Peer -} - -func newUdpAnnounceResponse(buf []byte) *udpAnnounceResponse { - response := &udpAnnounceResponse{ - action: binary.BigEndian.Uint32(buf[:4]), - transactionId: binary.BigEndian.Uint32(buf[4:8]), - interval: binary.BigEndian.Uint32(buf[8:12]), - leechers: binary.BigEndian.Uint32(buf[12:16]), - seeders: binary.BigEndian.Uint32(buf[16:20]), - } - count := (len(buf) - 20) / 6 - response.peers = make([]*peer.Peer, count, count) - for i := 0; i < count; i++ { - ipBegin := 20 + 6*i - portBegin := ipBegin + 4 - response.peers[i] = &peer.Peer{ - IP: binary.BigEndian.Uint32(buf[ipBegin:portBegin]), - Port: binary.BigEndian.Uint16(buf[portBegin : portBegin+2]), - } - } - return response -} - -type Tracker struct { - PeerID [20]byte - MetaInfo *metainfo.MetaInfo -} - -func (tracker *Tracker) connect(conn net.Conn, timeout int64) (response *udpConnectResponse, err error) { - request := newUdpConnectRequest() - buf := request.encode() - conn.SetReadDeadline(time.Now().Add(time.Second * time.Duration(timeout))) - conn.Write(buf) - len, err := conn.Read(buf) - if err != nil { - return - } - if len != 16 { - err = NewTrackerError(ErrResponse, errors.New("invalid response")) - return - } - response = newUdpConnectResponse(buf) - return -} - -func (tracker *Tracker) announce(conn net.Conn, timeout int64, connectionId uint64) (response *udpAnnounceResponse, err error) { - request := newUdpAnnounceRequest(connectionId) - request.infoHash = tracker.MetaInfo.GetInfoHash() - request.peerID = tracker.PeerID - request.downloaded = 0 - request.uploaded = 0 - request.left = tracker.MetaInfo.GetTotalSize() - request.downloaded - request.event = 0 - request.ip = 0 - request.key = 0 - request.numWant = 200 - request.port = 6882 - encode := request.encode() - conn.SetReadDeadline(time.Now().Add(time.Second * time.Duration(timeout))) - conn.Write(encode) - buf := make([]byte, 20+request.numWant*6) - len, err := conn.Read(buf) - if err != nil { - return - } - if len < 20 { - err = errors.New("announce fail") - return - } - response = newUdpAnnounceResponse(buf[:len]) - return -} - -// 通过tracker服务器获取peer信息 -func (tracker *Tracker) Tracker() <-chan []*peer.Peer { - peersCh := make(chan []*peer.Peer) - metaInfo := tracker.MetaInfo - - checkAnnounceList := func(announceList [][]string) bool { - if len(announceList) > 0 { - for _, l := range announceList { - if len(l) > 0 { - return true - } - } - } - return false - } - - // 只要获取到了peers就直接返回 - if checkAnnounceList(metaInfo.AnnounceList) { - wg := sync.WaitGroup{} - for _, announceArr := range metaInfo.AnnounceList { - if len(announceArr) > 0 { - for _, announce := range announceArr { - wg.Add(1) - go func(announce string) { - peers, err := tracker.DoTracker(announce) - if err == nil { - peersCh <- peers - } - wg.Done() - }(announce) - } - } - } - go func() { - wg.Wait() - close(peersCh) - }() - } else { - go func() { - peers, err := tracker.DoTracker(metaInfo.Announce) - if err == nil { - peersCh <- peers - } - close(peersCh) - }() - } - return peersCh -} - -func (tracker *Tracker) DoTracker(announce string) (peers []*peer.Peer, err error) { - if announce != "" { - url, _ := url.Parse(announce) - switch url.Scheme { - case "http", "https": - return tracker.httpTracker(url) - case "udp", "udp4", "udp6": - return tracker.udpTracker(url) - default: - return nil, errors.New("unsupported protocol") - } - } - return nil, errors.New("empty announce") -} - -// http://bittorrent.org/beps/bep_0003.html#trackers -func (tracker *Tracker) httpTracker(url *url.URL) (peers []*peer.Peer, err error) { - metaInfo := tracker.MetaInfo - peerID := tracker.PeerID - - query := url.Query() - infoHash := metaInfo.GetInfoHash() - query.Add("info_hash", string(infoHash[:])) - query.Add("peer_id", string(peerID[:])) - query.Add("port", "6882") - query.Add("uploaded", "0") - query.Add("downloaded", "0") - query.Add("left", strconv.FormatInt(int64(metaInfo.GetTotalSize()), 10)) - url.RawQuery = query.Encode() - request, err := http.NewRequest("GET", url.String(), nil) - if err != nil { - return - } - var httpClient = &http.Client{ - Timeout: time.Second * time.Duration(15), - } - request.Header.Set("User-Agent", "gopeed/0.0.1") - response, err := httpClient.Do(request) - if err != nil { - return - } - defer response.Body.Close() - resp, err := ioutil.ReadAll(response.Body) - if err != nil { - if err != io.EOF { - return - } - } - // TODO Parse HTTP tracker response - fmt.Println(resp) - return nil, nil -} - -// http://bittorrent.org/beps/bep_0015.html -func (tracker *Tracker) udpTracker(url *url.URL) (peers []*peer.Peer, err error) { - conn, err := dial(url) - if err != nil { - return - } - defer conn.Close() - - announceResponse, err := func() (announceResponse *udpAnnounceResponse, err error) { - var connectResponse *udpConnectResponse - // 0:connect 1:announce - state := udpActionConnect - // 访问超时最多重试8次,当有请求成功则将重试次数重置为0 - for n := 0; n < udpConnectRetries; n++ { - // 15 * 2 ^ n seconds - timeout := int64(udpConnectTimeout * math.Pow(2, float64(n))) - switch state { - case udpActionConnect: - connectResponse, err = tracker.connect(conn, timeout) - if err != nil { - if oe, ok := err.(*net.OpError); ok && oe.Timeout() { - break - } else { - return - } - } - n = -1 - state = udpActionAnnounce - break - case udpActionAnnounce: - announceResponse, err = tracker.announce(conn, timeout, connectResponse.connectionId) - if err != nil { - if oe, ok := err.(*net.OpError); ok && oe.Timeout() { - break - } else { - return - } - } - return - } - } - return - }() - - if err != nil { - return - } - - return announceResponse.peers, nil -} - -func dial(trackerUrl *url.URL) (net.Conn, error) { - return net.Dial(trackerUrl.Scheme, trackerUrl.Host) -} - -func lookupIP(host string, scheme string) (ip net.IP, err error) { - ips, _ := net.LookupIP(host) - if len(ips) == 0 { - err = errors.New("no ips") - return - } - for _, ip = range ips { - switch scheme { - case "udp", "udp4": - if ip.To4() != nil { - return - } - case "udp6": - if ip.To4() == nil { - return - } - } - } - err = errors.New("no available ips ") - return -} diff --git a/pkg/protocol/bt/tracker/tracker_test.go b/pkg/protocol/bt/tracker/tracker_test.go deleted file mode 100644 index 56e30c98..00000000 --- a/pkg/protocol/bt/tracker/tracker_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package tracker - -import ( - "fmt" - "github.com/monkeyWie/gopeed-core/pkg/protocol/bt/metainfo" - "testing" -) - -func TestTracker_Tracker(t *testing.T) { - metaInfo, err := metainfo.ParseFromFile("../testdata/Game.of.Thrones.S08E05.720p.WEB.H264-MEMENTO.torrent") - if err != nil { - panic(err) - } - tracker := &Tracker{ - PeerID: [20]byte{}, - MetaInfo: metaInfo, - } - tracker.MetaInfo.Announce = "udp://tracker.opentrackr.org:1337/announce" - tracker.MetaInfo.AnnounceList = [][]string{} - peers := <-tracker.Tracker() - fmt.Println(len(peers)) -} diff --git a/pkg/protocol/http/client.go b/pkg/protocol/http/client.go deleted file mode 100644 index 68e17bf1..00000000 --- a/pkg/protocol/http/client.go +++ /dev/null @@ -1,171 +0,0 @@ -package http - -import ( - "fmt" - "io" - "mime" - "net/http" - "net/http/cookiejar" - "os" - "path" - "strconv" - "strings" - "sync" -) - -// Resolve return the file response to be downloaded -func Resolve(request *Request) (*Response, error) { - httpRequest, err := BuildHTTPRequest(request) - if err != nil { - return nil, err - } - // Use "Range" header to resolve - httpRequest.Header.Add("Range", "bytes=0-0") - httpClient := BuildHTTPClient() - response, err := httpClient.Do(httpRequest) - if err != nil { - return nil, err - } - defer response.Body.Close() - if response.StatusCode != 200 && response.StatusCode != 206 { - return nil, fmt.Errorf("response status error:%d", response.StatusCode) - } - ret := &Response{} - // Get file name by "Content-Disposition" - if contentDisposition := response.Header.Get("Content-Disposition"); contentDisposition != "" { - _, params, _ := mime.ParseMediaType(contentDisposition) - if filename := params["filename"]; filename != "" { - ret.Name = filename - } - } - // Get file name by URL - if ret.Name == "" { - ret.Name = path.Base(httpRequest.URL.Path) - } - // Unknown file name - if ret.Name == "" { - ret.Name = "unknown" - } - // Is support range - ret.Range = response.StatusCode == 206 - // Get file size - if ret.Range { - contentRange := response.Header.Get("Content-Range") - if contentRange != "" { - // e.g. bytes 0-1000/1001 => 1001 - total := subLastSlash(contentRange) - if total != "" && total != "*" { - parse, err := strconv.ParseInt(total, 10, 64) - if err != nil { - return nil, err - } - ret.Size = parse - } - } - } else { - contentLength := response.Header.Get("Content-Length") - if contentLength != "" { - ret.Size, _ = strconv.ParseInt(contentLength, 10, 64) - } - } - return ret, nil -} - -// Down -func Down(request *Request) error { - response, err := Resolve(request) - if err != nil { - return err - } - // allocate file - file, err := os.Create(response.Name) - if err != nil { - return err - } - defer file.Close() - if err := file.Truncate(response.Size); err != nil { - return err - } - // support range - if response.Range { - cons := 16 - chunkSize := response.Size / int64(cons) - waitGroup := &sync.WaitGroup{} - waitGroup.Add(cons) - for i := 0; i < cons; i++ { - start := int64(i) * chunkSize - end := start + chunkSize - if i == cons-1 { - end = response.Size - } - go downChunk(request, file, start, end-1, waitGroup) - } - waitGroup.Wait() - } else { - downChunk(request, file, 0, response.Size, nil) - } - return nil -} - -func subLastSlash(str string) string { - index := strings.LastIndex(str, "/") - if index != -1 { - return str[index+1:] - } - return "" -} - -func BuildHTTPRequest(request *Request) (*http.Request, error) { - // Build request - httpRequest, err := http.NewRequest(strings.ToUpper(request.Method), request.URL, nil) - if err != nil { - return nil, err - } - for k, v := range request.Header { - httpRequest.Header.Add(k, v) - } - return httpRequest, nil -} - -func BuildHTTPClient() *http.Client { - // Cookie handle - jar, _ := cookiejar.New(nil) - - return &http.Client{Jar: jar} -} - -func downChunk(request *Request, file *os.File, start int64, end int64, waitGroup *sync.WaitGroup) { - if waitGroup != nil { - defer waitGroup.Done() - } - httpRequest, _ := BuildHTTPRequest(request) - httpRequest.Header.Add("Range", fmt.Sprintf("bytes=%d-%d", start, end)) - fmt.Printf("down %d-%d\n", start, end) - httpClient := BuildHTTPClient() - httpResponse, err := httpClient.Do(httpRequest) - if err != nil { - fmt.Println(err) - return - } - defer httpResponse.Body.Close() - buf := make([]byte, 8192) - writeIndex := start - for { - n, err := httpResponse.Body.Read(buf) - if n > 0 { - writeSize, err := file.WriteAt(buf[0:n], writeIndex) - if err != nil { - fmt.Println(err) - return - } - writeIndex += int64(writeSize) - } - if err != nil { - if err != io.EOF { - fmt.Println(err) - return - } - break - } - } -} diff --git a/pkg/protocol/http/client_test.go b/pkg/protocol/http/client_test.go deleted file mode 100644 index 30f9b6ed..00000000 --- a/pkg/protocol/http/client_test.go +++ /dev/null @@ -1,154 +0,0 @@ -package http - -import ( - "crypto/md5" - "encoding/hex" - "fmt" - "io" - "log" - "os" - "reflect" - "testing" - "time" -) - -func TestDown(t *testing.T) { - type args struct { - request *Request - } - tests := []struct { - name string - args args - }{ - { - "proxyee-down.ico", - args{ - &Request{ - "get", - "https://raw.githubusercontent.com/proxyee-down-org/proxyee-down/master/front/public/favicon.ico", - map[string]string{ - "Host": "github.com", - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", - "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", - "Referer": "http://github.com/proxyee-down-org/proxyee-down/releases", - "Accept-Encoding": "gzip, deflate, br", - "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", - }, - nil, - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - os.Remove("favicon.ico") - err := Down(tt.args.request) - if err != nil { - t.Errorf("error down= %v", err) - return - } - downMd5 := fileMd5("favicon.ico") - os.Remove("favicon.ico") - if "8de7a6a2e786861013d61b77b2394012" != downMd5 { - t.Errorf("error md5= %v", downMd5) - return - } - }) - } -} - -func TestResolve(t *testing.T) { - // os.Setenv("HTTP_PROXY", "http://127.0.0.1:8888") - type args struct { - request *Request - } - tests := []struct { - name string - args args - want *Response - wantErr bool - }{ - { - "proxyee-down.ico", - args{ - &Request{ - "get", - "https://raw.githubusercontent.com/proxyee-down-org/proxyee-down/master/front/public/favicon.ico", - map[string]string{ - "Host": "github.com", - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", - "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", - "Referer": "http://github.com/proxyee-down-org/proxyee-down/releases", - "Accept-Encoding": "gzip, deflate, br", - "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", - }, - nil, - }, - }, - &Response{"favicon.ico", 116093, true}, - false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := Resolve(tt.args.request) - if (err != nil) != tt.wantErr { - t.Errorf("Resolve() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("Resolve() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestTemp(t *testing.T) { - f, err := os.Create("e:/testbt/test.data") - if err != nil { - log.Fatal(err) - } - if err := f.Truncate(1024 * 1024 * 512); err != nil { - log.Fatal(err) - } - bts := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9} - s := time.Now().UnixNano() - f.Write(bts) - fmt.Printf("write from 0 use: %d\n", time.Now().UnixNano()-s) - s = time.Now().UnixNano() - f.Seek(1024*1024*128, 0) - f.Write(bts) - fmt.Printf("write from 128M use %d\n", time.Now().UnixNano()-s) - s = time.Now().UnixNano() - f.Write(bts) - fmt.Printf("write from 128M+9b use %d\n", time.Now().UnixNano()-s) - s = time.Now().UnixNano() - f.Seek(0, 0) - f.Write(bts) - fmt.Printf("write from 0 agein use %d\n", time.Now().UnixNano()-s) - s = time.Now().UnixNano() - f.Seek(1024*1024*64, 0) - f.Write(bts) - fmt.Printf("write from agein 64M use %d\n", time.Now().UnixNano()-s) -} - -func fileMd5(filePath string) string { - file, _ := os.Open(filePath) - - // Tell the program to call the following function when the current function returns - defer file.Close() - - // Open a new hash interface to write to - hash := md5.New() - - // Copy the file in the hash interface and check for any error - if _, err := io.Copy(hash, file); err != nil { - return "" - } - return hex.EncodeToString(hash.Sum(nil)) -} - -func TestTemp2(t *testing.T) { - fmt.Println("123456789"[2:3]) -} diff --git a/pkg/protocol/http/request.go b/pkg/protocol/http/request.go deleted file mode 100644 index 45b11372..00000000 --- a/pkg/protocol/http/request.go +++ /dev/null @@ -1,8 +0,0 @@ -package http - -type Request struct { - Method string - URL string - Header map[string]string - Content []byte -} diff --git a/pkg/protocol/http/response.go b/pkg/protocol/http/response.go deleted file mode 100644 index 4bfe9949..00000000 --- a/pkg/protocol/http/response.go +++ /dev/null @@ -1,7 +0,0 @@ -package http - -type Response struct { - Name string - Size int64 - Range bool -}