├── .github └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── README.md ├── bencode ├── AUTHORS ├── LICENSE ├── decode.go ├── decode_test.go ├── doc.go ├── encode.go ├── encode_decode_test.go ├── encode_test.go ├── example_test.go ├── iszero_go1.12.go ├── iszero_go1.13.go ├── raw.go └── tag.go ├── dht ├── blacklist.go ├── blacklist_test.go ├── dht_server.go ├── dht_server_test.go ├── peer_manager.go ├── routing_table.go ├── routing_table_storage.go ├── token_manager.go └── transaction_manager.go ├── downloader ├── block_handler.go ├── doc.go ├── torrent.go └── torrent_test.go ├── go.mod ├── go.sum ├── internal └── helper │ ├── io.go │ ├── slice.go │ ├── slice_test.go │ └── string.go ├── krpc ├── addr.go ├── addr_test.go ├── doc.go ├── message.go ├── message_test.go └── node.go ├── metainfo ├── addr_compact.go ├── addr_compact_test.go ├── addr_host.go ├── addr_host_test.go ├── doc.go ├── file.go ├── info.go ├── info_test.go ├── infohash.go ├── infohash_test.go ├── magnet.go ├── metainfo.go ├── piece.go ├── piece_test.go ├── reader.go ├── reader_writer_test.go └── writer.go ├── peerprotocol ├── doc.go ├── extension.go ├── extension_test.go ├── fastset.go ├── fastset_test.go ├── handshake.go ├── message.go ├── message_test.go ├── noop_handler.go ├── peerconn.go ├── protocol.go └── server.go └── tracker ├── get_peers.go ├── httptracker ├── go1.13.go ├── go1.13_compat.go ├── http.go ├── http_peer.go ├── http_peer_test.go └── http_test.go ├── tracker.go ├── tracker_test.go └── udptracker ├── udp.go ├── udp_client.go ├── udp_server.go └── udp_test.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | on: push 3 | env: 4 | GO111MODULE: on 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | name: Go ${{ matrix.go }} 9 | strategy: 10 | matrix: 11 | go: 12 | - "1.11" 13 | - "1.12" 14 | - "1.13" 15 | - "1.14" 16 | - "1.15" 17 | - "1.16" 18 | - "1.17" 19 | - "1.18" 20 | - "1.19" 21 | - "1.20" 22 | - "1.21" 23 | - "1.22" 24 | - "1.23" 25 | steps: 26 | - uses: actions/checkout@v4 27 | - name: Setup Go 28 | uses: actions/setup-go@v5 29 | with: 30 | go-version: ${{ matrix.go }} 31 | - run: go test -race -cover ./... 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | # Intellij IDE 27 | .idea/ 28 | *.iml 29 | 30 | # test 31 | test_* 32 | 33 | # log 34 | *.log 35 | 36 | vendor/ 37 | 38 | # VS Code 39 | .vscode/ 40 | debug 41 | debug_test 42 | 43 | # Mac 44 | .DS_Store 45 | 46 | # hidden files 47 | .* 48 | _* 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BT - Another Implementation For Golang [![Build Status](https://github.com/xgfone/go-bt/actions/workflows/go.yml/badge.svg)](https://github.com/xgfone/go-bt/actions/workflows/go.yml) [![GoDoc](https://pkg.go.dev/badge/github.com/xgfone/go-bt)](https://pkg.go.dev/github.com/xgfone/go-bt) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=flat-square)](https://raw.githubusercontent.com/xgfone/go-bt/master/LICENSE) 2 | 3 | A pure golang implementation of [BitTorrent](http://bittorrent.org/beps/bep_0000.html) library, which is inspired by [dht](https://github.com/shiyanhui/dht) and [torrent](https://github.com/anacrolix/torrent). 4 | 5 | ## Install 6 | 7 | ```shell 8 | $ go get -u github.com/xgfone/go-bt 9 | ``` 10 | 11 | ## Features 12 | 13 | - Support `Go1.11+`. 14 | - Support IPv4/IPv6. 15 | - Multi-BEPs implementation. 16 | - Pure Go implementation without `CGO`. 17 | - Only library without any denpendencies. For the command tools, see [bttools](https://github.com/xgfone/bttools). 18 | 19 | ## The Implemented Specifications 20 | 21 | - [x] [**BEP 03:** The BitTorrent Protocol Specification](http://bittorrent.org/beps/bep_0003.html) 22 | - [x] [**BEP 05:** DHT Protocol](http://bittorrent.org/beps/bep_0005.html) 23 | - [x] [**BEP 06:** Fast Extension](http://bittorrent.org/beps/bep_0006.html) 24 | - [x] [**BEP 07:** IPv6 Tracker Extension](http://bittorrent.org/beps/bep_0007.html) 25 | - [x] [**BEP 09:** Extension for Peers to Send Metadata Files](http://bittorrent.org/beps/bep_0009.html) 26 | - [x] [**BEP 10:** Extension Protocol](http://bittorrent.org/beps/bep_0010.html) 27 | - [ ] [**BEP 11:** Peer Exchange (PEX)](http://bittorrent.org/beps/bep_0011.html) 28 | - [x] [**BEP 12:** Multitracker Metadata Extension](http://bittorrent.org/beps/bep_0012.html) 29 | - [x] [**BEP 15:** UDP Tracker Protocol for BitTorrent](http://bittorrent.org/beps/bep_0015.html) 30 | - [x] [**BEP 19:** WebSeed - HTTP/FTP Seeding (GetRight style)](http://bittorrent.org/beps/bep_0019.html) (Only `url-list` in metainfo) 31 | - [x] [**BEP 23:** Tracker Returns Compact Peer Lists](http://bittorrent.org/beps/bep_0023.html) 32 | - [x] [**BEP 32:** IPv6 extension for DHT](http://bittorrent.org/beps/bep_0032.html) 33 | - [ ] [**BEP 33:** DHT scrape](http://bittorrent.org/beps/bep_0033.html) 34 | - [x] [**BEP 41:** UDP Tracker Protocol Extensions](http://bittorrent.org/beps/bep_0041.html) 35 | - [x] [**BEP 43:** Read-only DHT Nodes](http://bittorrent.org/beps/bep_0043.html) 36 | - [ ] [**BEP 44:** Storing arbitrary data in the DHT](http://bittorrent.org/beps/bep_0044.html) 37 | - [x] [**BEP 48:** Tracker Protocol Extension: Scrape](http://bittorrent.org/beps/bep_0048.html) 38 | 39 | ## Example 40 | 41 | See [godoc](https://pkg.go.dev/github.com/xgfone/go-bt) or [bttools](https://github.com/xgfone/bttools). 42 | -------------------------------------------------------------------------------- /bencode/AUTHORS: -------------------------------------------------------------------------------- 1 | Jeff Wendling 2 | Liam Edwards-Playne 3 | Casey Bodley 4 | Conrad Pankoff 5 | Cenk Alti 6 | Jan Winkelmann 7 | Patrick Mézard 8 | Glen De Cauwsemaecker 9 | -------------------------------------------------------------------------------- /bencode/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 The Authors (see AUTHORS) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /bencode/doc.go: -------------------------------------------------------------------------------- 1 | // Package bencode implements encoding and decoding of bencoded objects, 2 | // which has a similar API to the encoding/json package and many other 3 | // serialization formats. 4 | // 5 | // Notice: the package is ported from github.com/zeebo/bencode@v1.0.0 6 | package bencode 7 | -------------------------------------------------------------------------------- /bencode/encode.go: -------------------------------------------------------------------------------- 1 | package bencode 2 | 3 | import ( 4 | "bytes" 5 | "encoding" 6 | "fmt" 7 | "io" 8 | "reflect" 9 | "sort" 10 | ) 11 | 12 | type sortValues []reflect.Value 13 | 14 | func (p sortValues) Len() int { return len(p) } 15 | func (p sortValues) Less(i, j int) bool { return p[i].String() < p[j].String() } 16 | func (p sortValues) Swap(i, j int) { p[i], p[j] = p[j], p[i] } 17 | 18 | // Marshaler is the interface implemented by types 19 | // that can marshal themselves into valid bencode. 20 | type Marshaler interface { 21 | MarshalBencode() ([]byte, error) 22 | } 23 | 24 | // An Encoder writes bencoded objects to an output stream. 25 | type Encoder struct { 26 | w io.Writer 27 | } 28 | 29 | // NewEncoder returns a new encoder that writes to w. 30 | func NewEncoder(w io.Writer) *Encoder { 31 | return &Encoder{w} 32 | } 33 | 34 | // Encode writes the bencoded data of val to its output stream. 35 | // If an encountered value implements the Marshaler interface, 36 | // its MarshalBencode method is called to produce the bencode output for this value. 37 | // If no MarshalBencode method is present but the value implements encoding.TextMarshaler instead, 38 | // its MarshalText method is called, which encodes the result as a bencode string. 39 | // See the documentation for Decode about the conversion of Go values to 40 | // bencoded data. 41 | func (e *Encoder) Encode(val interface{}) error { 42 | return encodeValue(e.w, reflect.ValueOf(val)) 43 | } 44 | 45 | // EncodeString returns the bencoded data of val as a string. 46 | func EncodeString(val interface{}) (string, error) { 47 | buf := new(bytes.Buffer) 48 | e := NewEncoder(buf) 49 | if err := e.Encode(val); err != nil { 50 | return "", err 51 | } 52 | return buf.String(), nil 53 | } 54 | 55 | // EncodeBytes returns the bencoded data of val as a slice of bytes. 56 | func EncodeBytes(val interface{}) ([]byte, error) { 57 | buf := new(bytes.Buffer) 58 | e := NewEncoder(buf) 59 | if err := e.Encode(val); err != nil { 60 | return nil, err 61 | } 62 | return buf.Bytes(), nil 63 | } 64 | 65 | func isNilValue(v reflect.Value) bool { 66 | return (v.Kind() == reflect.Interface || v.Kind() == reflect.Ptr) && 67 | v.IsNil() 68 | } 69 | 70 | func encodeValue(w io.Writer, val reflect.Value) error { 71 | marshaler, textMarshaler, v := indirectEncodeValue(val) 72 | 73 | // marshal a type using the Marshaler type 74 | // if it implements that interface. 75 | if marshaler != nil { 76 | bytes, err := marshaler.MarshalBencode() 77 | if err != nil { 78 | return err 79 | } 80 | 81 | _, err = w.Write(bytes) 82 | return err 83 | } 84 | 85 | // marshal a type using the TextMarshaler type 86 | // if it implements that interface. 87 | if textMarshaler != nil { 88 | bytes, err := textMarshaler.MarshalText() 89 | if err != nil { 90 | return err 91 | } 92 | 93 | _, err = fmt.Fprintf(w, "%d:%s", len(bytes), bytes) 94 | return err 95 | } 96 | 97 | // if indirection returns us an invalid value that means there was a nil 98 | // pointer in the path somewhere. 99 | if !v.IsValid() { 100 | return nil 101 | } 102 | 103 | // send in a raw message if we have that type 104 | if rm, ok := v.Interface().(RawMessage); ok { 105 | _, err := io.Copy(w, bytes.NewReader(rm)) 106 | return err 107 | } 108 | 109 | switch v.Kind() { 110 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 111 | _, err := fmt.Fprintf(w, "i%de", v.Int()) 112 | return err 113 | 114 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 115 | _, err := fmt.Fprintf(w, "i%de", v.Uint()) 116 | return err 117 | 118 | case reflect.Bool: 119 | i := 0 120 | if v.Bool() { 121 | i = 1 122 | } 123 | _, err := fmt.Fprintf(w, "i%de", i) 124 | return err 125 | 126 | case reflect.String: 127 | _, err := fmt.Fprintf(w, "%d:%s", len(v.String()), v.String()) 128 | return err 129 | 130 | case reflect.Slice, reflect.Array: 131 | // handle byte slices like strings 132 | if byteSlice, ok := val.Interface().([]byte); ok { 133 | _, err := fmt.Fprintf(w, "%d:", len(byteSlice)) 134 | 135 | if err == nil { 136 | _, err = w.Write(byteSlice) 137 | } 138 | 139 | return err 140 | } 141 | 142 | if _, err := fmt.Fprint(w, "l"); err != nil { 143 | return err 144 | } 145 | 146 | for i := 0; i < v.Len(); i++ { 147 | if err := encodeValue(w, v.Index(i)); err != nil { 148 | return err 149 | } 150 | } 151 | 152 | _, err := fmt.Fprint(w, "e") 153 | return err 154 | 155 | case reflect.Map: 156 | if _, err := fmt.Fprint(w, "d"); err != nil { 157 | return err 158 | } 159 | var ( 160 | keys sortValues = v.MapKeys() 161 | mval reflect.Value 162 | ) 163 | sort.Sort(keys) 164 | for i := range keys { 165 | mval = v.MapIndex(keys[i]) 166 | if isNilValue(mval) { 167 | continue 168 | } 169 | if err := encodeValue(w, keys[i]); err != nil { 170 | return err 171 | } 172 | if err := encodeValue(w, mval); err != nil { 173 | return err 174 | } 175 | } 176 | _, err := fmt.Fprint(w, "e") 177 | return err 178 | 179 | case reflect.Struct: 180 | if _, err := fmt.Fprint(w, "d"); err != nil { 181 | return err 182 | } 183 | 184 | // add embedded structs to the dictionary 185 | dict := make(dictionary, 0, v.NumField()) 186 | dict, err := readStruct(dict, v) 187 | if err != nil { 188 | return err 189 | } 190 | 191 | // sort the dictionary by keys 192 | sort.Sort(dict) 193 | 194 | // encode the dictionary in order 195 | for _, def := range dict { 196 | // encode the key 197 | err := encodeValue(w, reflect.ValueOf(def.key)) 198 | if err != nil { 199 | return err 200 | } 201 | 202 | // encode the value 203 | err = encodeValue(w, def.value) 204 | if err != nil { 205 | return err 206 | } 207 | } 208 | 209 | _, err = fmt.Fprint(w, "e") 210 | return err 211 | } 212 | 213 | return fmt.Errorf("Can't encode type: %s", v.Type()) 214 | } 215 | 216 | // indirectEncodeValue walks down v allocating pointers as needed, 217 | // until it gets to a non-pointer. 218 | // if it encounters an (Text)Marshaler, indirect stops and returns that. 219 | func indirectEncodeValue(v reflect.Value) (Marshaler, encoding.TextMarshaler, reflect.Value) { 220 | // If v is a named type and is addressable, 221 | // start with its address, so that if the type has pointer methods, 222 | // we find them. 223 | if v.Kind() != reflect.Ptr && v.Type().Name() != "" && v.CanAddr() { 224 | v = v.Addr() 225 | } 226 | for { 227 | if v.Kind() == reflect.Ptr && v.IsNil() { 228 | break 229 | } 230 | 231 | vi := v.Interface() 232 | if m, ok := vi.(Marshaler); ok { 233 | return m, nil, reflect.Value{} 234 | } 235 | if m, ok := vi.(encoding.TextMarshaler); ok { 236 | return nil, m, reflect.Value{} 237 | } 238 | 239 | if v.Kind() != reflect.Ptr { 240 | break 241 | } 242 | 243 | v = v.Elem() 244 | } 245 | return nil, nil, indirect(v, false) 246 | } 247 | 248 | type definition struct { 249 | key string 250 | value reflect.Value 251 | } 252 | 253 | type dictionary []definition 254 | 255 | func (d dictionary) Len() int { return len(d) } 256 | func (d dictionary) Less(i, j int) bool { return d[i].key < d[j].key } 257 | func (d dictionary) Swap(i, j int) { d[i], d[j] = d[j], d[i] } 258 | 259 | func readStruct(dict dictionary, v reflect.Value) (dictionary, error) { 260 | t := v.Type() 261 | var ( 262 | fieldValue reflect.Value 263 | rkey string 264 | ) 265 | for i := 0; i < t.NumField(); i++ { 266 | key := t.Field(i) 267 | rkey = key.Name 268 | fieldValue = v.FieldByIndex(key.Index) 269 | 270 | // filter out unexported values etc. 271 | if !fieldValue.CanInterface() { 272 | continue 273 | } 274 | 275 | // filter out nil pointer values 276 | if isNilValue(fieldValue) { 277 | continue 278 | } 279 | 280 | // * Near identical to usage in JSON except with key 'bencode' 281 | // 282 | // * Struct values encode as BEncode dictionaries. Each exported 283 | // struct field becomes a set in the dictionary unless 284 | // - the field's tag is "-", or 285 | // - the field is empty and its tag specifies the "omitempty" 286 | // option. 287 | // 288 | // * The default key string is the struct field name but can be 289 | // specified in the struct field's tag value. The "bencode" 290 | // key in struct field's tag value is the key name, followed 291 | // by an optional comma and options. 292 | tagValue := key.Tag.Get("bencode") 293 | if tagValue != "" { 294 | // Keys with '-' are omit from output 295 | if tagValue == "-" { 296 | continue 297 | } 298 | 299 | name, options := parseTag(tagValue) 300 | // Keys with 'omitempty' are omitted if the field is empty 301 | if options.Contains("omitempty") && isEmptyValue(fieldValue) { 302 | continue 303 | } 304 | 305 | // All other values are treated as the key string 306 | if isValidTag(name) { 307 | rkey = name 308 | } 309 | } 310 | 311 | if key.Anonymous && key.Type.Kind() == reflect.Struct && tagValue == "" { 312 | var err error 313 | dict, err = readStruct(dict, fieldValue) 314 | if err != nil { 315 | return nil, err 316 | } 317 | } else { 318 | dict = append(dict, definition{rkey, fieldValue}) 319 | } 320 | } 321 | return dict, nil 322 | } 323 | -------------------------------------------------------------------------------- /bencode/encode_decode_test.go: -------------------------------------------------------------------------------- 1 | package bencode 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | ) 7 | 8 | type myBoolType bool 9 | 10 | // MarshalBencode implements Marshaler.MarshalBencode 11 | func (mbt myBoolType) MarshalBencode() ([]byte, error) { 12 | var c string 13 | if mbt { 14 | c = "y" 15 | } else { 16 | c = "n" 17 | } 18 | 19 | return EncodeBytes(c) 20 | } 21 | 22 | // UnmarshalBencode implements Unmarshaler.UnmarshalBencode 23 | func (mbt *myBoolType) UnmarshalBencode(b []byte) error { 24 | var str string 25 | err := DecodeBytes(b, &str) 26 | if err != nil { 27 | return err 28 | } 29 | 30 | switch str { 31 | case "y": 32 | *mbt = true 33 | case "n": 34 | *mbt = false 35 | default: 36 | err = errors.New("invalid myBoolType") 37 | } 38 | 39 | return err 40 | } 41 | 42 | type myBoolTextType bool 43 | 44 | // MarshalText implements TextMarshaler.MarshalText 45 | func (mbt myBoolTextType) MarshalText() ([]byte, error) { 46 | if mbt { 47 | return []byte("y"), nil 48 | } 49 | 50 | return []byte("n"), nil 51 | } 52 | 53 | // UnmarshalText implements TextUnmarshaler.UnmarshalText 54 | func (mbt *myBoolTextType) UnmarshalText(b []byte) error { 55 | switch string(b) { 56 | case "y": 57 | *mbt = true 58 | case "n": 59 | *mbt = false 60 | default: 61 | return errors.New("invalid myBoolType") 62 | } 63 | return nil 64 | } 65 | 66 | type myTimeType struct { 67 | time.Time 68 | } 69 | 70 | // MarshalBencode implements Marshaler.MarshalBencode 71 | func (mtt myTimeType) MarshalBencode() ([]byte, error) { 72 | return EncodeBytes(mtt.Time.Unix()) 73 | } 74 | 75 | // UnmarshalBencode implements Unmarshaler.UnmarshalBencode 76 | func (mtt *myTimeType) UnmarshalBencode(b []byte) error { 77 | var epoch int64 78 | err := DecodeBytes(b, &epoch) 79 | if err != nil { 80 | return err 81 | } 82 | 83 | mtt.Time = time.Unix(epoch, 0) 84 | return nil 85 | } 86 | 87 | type errorMarshalType struct{} 88 | 89 | // MarshalBencode implements Marshaler.MarshalBencode 90 | func (emt errorMarshalType) MarshalBencode() ([]byte, error) { 91 | return nil, errors.New("oops") 92 | } 93 | 94 | // UnmarshalBencode implements Unmarshaler.UnmarshalBencode 95 | func (emt errorMarshalType) UnmarshalBencode([]byte) error { 96 | return errors.New("oops") 97 | } 98 | 99 | type errorTextMarshalType struct{} 100 | 101 | // MarshalText implements TextMarshaler.MarshalText 102 | func (emt errorTextMarshalType) MarshalText() ([]byte, error) { 103 | return nil, errors.New("oops") 104 | } 105 | 106 | // UnmarshalText implements TextUnmarshaler.UnmarshalText 107 | func (emt errorTextMarshalType) UnmarshalText([]byte) error { 108 | return errors.New("oops") 109 | } 110 | -------------------------------------------------------------------------------- /bencode/example_test.go: -------------------------------------------------------------------------------- 1 | package bencode 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | ) 7 | 8 | var ( 9 | data string 10 | r io.Reader 11 | w io.Writer 12 | ) 13 | 14 | func ExampleDecodeString() { 15 | var torrent interface{} 16 | if err := DecodeString(data, &torrent); err != nil { 17 | panic(err) 18 | } 19 | } 20 | 21 | func ExampleEncodeString() { 22 | var torrent interface{} 23 | data, err := EncodeString(torrent) 24 | if err != nil { 25 | panic(err) 26 | } 27 | fmt.Println(data) 28 | } 29 | 30 | func ExampleDecodeBytes() { 31 | var torrent interface{} 32 | if err := DecodeBytes([]byte(data), &torrent); err != nil { 33 | panic(err) 34 | } 35 | } 36 | 37 | func ExampleEncodeBytes() { 38 | var torrent interface{} 39 | data, err := EncodeBytes(torrent) 40 | if err != nil { 41 | panic(err) 42 | } 43 | fmt.Println(data) 44 | } 45 | 46 | func ExampleEncoder_Encode() { 47 | var x struct { 48 | Foo string 49 | Bar []string `bencode:"name"` 50 | } 51 | 52 | enc := NewEncoder(w) 53 | if err := enc.Encode(x); err != nil { 54 | panic(err) 55 | } 56 | } 57 | 58 | func ExampleDecoder_Decode() { 59 | dec := NewDecoder(r) 60 | var torrent struct { 61 | Announce string 62 | List [][]string `bencode:"announce-list"` 63 | } 64 | if err := dec.Decode(&torrent); err != nil { 65 | panic(err) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /bencode/iszero_go1.12.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // +build !go1.13 16 | 17 | package bencode 18 | 19 | import ( 20 | "math" 21 | "reflect" 22 | ) 23 | 24 | func isZero(v reflect.Value) bool { 25 | switch v.Kind() { 26 | case reflect.Bool: 27 | return !v.Bool() 28 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 29 | return v.Int() == 0 30 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, 31 | reflect.Uint64, reflect.Uintptr: 32 | return v.Uint() == 0 33 | case reflect.Float32, reflect.Float64: 34 | return math.Float64bits(v.Float()) == 0 35 | case reflect.Complex64, reflect.Complex128: 36 | c := v.Complex() 37 | return math.Float64bits(real(c)) == 0 && math.Float64bits(imag(c)) == 0 38 | case reflect.Array: 39 | for i := 0; i < v.Len(); i++ { 40 | if !isZero(v.Index(i)) { 41 | return false 42 | } 43 | } 44 | return true 45 | case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, 46 | reflect.Ptr, reflect.Slice, reflect.UnsafePointer: 47 | return v.IsNil() 48 | case reflect.String: 49 | return v.Len() == 0 50 | case reflect.Struct: 51 | for i := 0; i < v.NumField(); i++ { 52 | if !isZero(v.Field(i)) { 53 | return false 54 | } 55 | } 56 | return true 57 | default: 58 | // This should never happens, but will act as a safeguard for 59 | // later, as a default value doesn't makes sense here. 60 | panic(&reflect.ValueError{Method: "reflect.Value.IsZero", Kind: v.Kind()}) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /bencode/iszero_go1.13.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // +build go1.13 16 | 17 | package bencode 18 | 19 | import "reflect" 20 | 21 | func isZero(v reflect.Value) bool { return v.IsZero() } 22 | -------------------------------------------------------------------------------- /bencode/raw.go: -------------------------------------------------------------------------------- 1 | package bencode 2 | 3 | // RawMessage is a special type that will store the raw bencode data when 4 | // encoding or decoding. 5 | type RawMessage []byte 6 | -------------------------------------------------------------------------------- /bencode/tag.go: -------------------------------------------------------------------------------- 1 | package bencode 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | "unicode" 7 | ) 8 | 9 | // tagOptions is the string following a comma in a struct field's "bencode" 10 | // tag, or the empty string. It does not include the leading comma. 11 | type tagOptions string 12 | 13 | // parseTag splits a struct field's tag into its name and 14 | // comma-separated options. 15 | func parseTag(tag string) (string, tagOptions) { 16 | if idx := strings.Index(tag, ","); idx != -1 { 17 | return tag[:idx], tagOptions(tag[idx+1:]) 18 | } 19 | return tag, tagOptions("") 20 | } 21 | 22 | // Contains returns whether checks that a comma-separated list of options 23 | // contains a particular substr flag. substr must be surrounded by a 24 | // string boundary or commas. 25 | func (options tagOptions) Contains(optionName string) bool { 26 | s := string(options) 27 | for s != "" { 28 | var next string 29 | i := strings.Index(s, ",") 30 | if i >= 0 { 31 | s, next = s[:i], s[i+1:] 32 | } 33 | if s == optionName { 34 | return true 35 | } 36 | s = next 37 | } 38 | return false 39 | } 40 | 41 | func isValidTag(key string) bool { 42 | if key == "" { 43 | return false 44 | } 45 | for _, c := range key { 46 | if c != ' ' && c != '$' && c != '-' && c != '_' && c != '.' && !unicode.IsLetter(c) && !unicode.IsDigit(c) { 47 | return false 48 | } 49 | } 50 | return true 51 | } 52 | 53 | func matchName(key string) func(string) bool { 54 | return func(s string) bool { 55 | return strings.ToLower(key) == strings.ToLower(s) 56 | } 57 | } 58 | 59 | func isEmptyValue(v reflect.Value) bool { 60 | switch v.Kind() { 61 | case reflect.Array, reflect.Map, reflect.Slice, reflect.String: 62 | return v.Len() == 0 63 | case reflect.Bool: 64 | return !v.Bool() 65 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 66 | return v.Int() == 0 67 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 68 | return v.Uint() == 0 69 | case reflect.Float32, reflect.Float64: 70 | return v.Float() == 0 71 | case reflect.Interface, reflect.Ptr: 72 | return v.IsNil() 73 | default: 74 | return isZero(v) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /dht/blacklist.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package dht 16 | 17 | import ( 18 | "sync" 19 | "time" 20 | 21 | "github.com/xgfone/go-bt/krpc" 22 | ) 23 | 24 | // Blacklist is used to manage the ip blacklist. 25 | // 26 | // Notice: The implementation should clear the address existed for long time. 27 | type Blacklist interface { 28 | // In reports whether the address, ip and port, is in the blacklist. 29 | In(krpc.Addr) bool 30 | 31 | // If port is equal to 0, it should ignore port and only use ip when matching. 32 | Add(krpc.Addr) 33 | 34 | // If port is equal to 0, it should delete the address by only the ip. 35 | Del(krpc.Addr) 36 | 37 | // Close is used to notice the implementation to release the underlying resource. 38 | Close() 39 | } 40 | 41 | type noopBlacklist struct{} 42 | 43 | func (nbl noopBlacklist) In(krpc.Addr) bool { return false } 44 | func (nbl noopBlacklist) Add(krpc.Addr) {} 45 | func (nbl noopBlacklist) Del(krpc.Addr) {} 46 | func (nbl noopBlacklist) Close() {} 47 | 48 | // NewNoopBlacklist returns a no-op Blacklist. 49 | func NewNoopBlacklist() Blacklist { return noopBlacklist{} } 50 | 51 | // DebugBlacklist returns a new Blacklist to log the information as debug. 52 | func DebugBlacklist(bl Blacklist, logf func(string, ...interface{})) Blacklist { 53 | return logBlacklist{Blacklist: bl, logf: logf} 54 | } 55 | 56 | type logBlacklist struct { 57 | Blacklist 58 | logf func(string, ...interface{}) 59 | } 60 | 61 | func (l logBlacklist) Add(addr krpc.Addr) { 62 | l.logf("add the addr '%s' into the blacklist", addr.String()) 63 | l.Blacklist.Add(addr) 64 | } 65 | 66 | func (l logBlacklist) Del(addr krpc.Addr) { 67 | l.logf("delete the addr '%s' from the blacklist", addr.String()) 68 | l.Blacklist.Del(addr) 69 | } 70 | 71 | /// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 72 | 73 | // NewMemoryBlacklist returns a blacklst implementation based on memory. 74 | // 75 | // if maxnum is equal to 0, no limit. 76 | func NewMemoryBlacklist(maxnum int, duration time.Duration) Blacklist { 77 | bl := &blacklist{ 78 | num: maxnum, 79 | ips: make(map[string]*wrappedPort, 128), 80 | exit: make(chan struct{}), 81 | } 82 | go bl.loop(duration) 83 | return bl 84 | } 85 | 86 | type wrappedPort struct { 87 | Time time.Time 88 | Enable bool 89 | Ports map[uint16]struct{} 90 | } 91 | 92 | type blacklist struct { 93 | exit chan struct{} 94 | lock sync.RWMutex 95 | ips map[string]*wrappedPort 96 | num int 97 | } 98 | 99 | func (bl *blacklist) loop(interval time.Duration) { 100 | tick := time.NewTicker(interval) 101 | defer tick.Stop() 102 | for { 103 | select { 104 | case <-bl.exit: 105 | return 106 | case now := <-tick.C: 107 | bl.lock.Lock() 108 | for ip, wp := range bl.ips { 109 | if now.Sub(wp.Time) > interval { 110 | delete(bl.ips, ip) 111 | } 112 | } 113 | bl.lock.Unlock() 114 | } 115 | } 116 | } 117 | 118 | func (bl *blacklist) Close() { 119 | select { 120 | case <-bl.exit: 121 | default: 122 | close(bl.exit) 123 | } 124 | } 125 | 126 | // In reports whether the address, ip and port, is in the blacklist. 127 | func (bl *blacklist) In(addr krpc.Addr) (yes bool) { 128 | bl.lock.RLock() 129 | if wp, ok := bl.ips[addr.IP.String()]; ok { 130 | if wp.Enable { 131 | _, yes = wp.Ports[addr.Port] 132 | } else { 133 | yes = true 134 | } 135 | } 136 | bl.lock.RUnlock() 137 | return 138 | } 139 | 140 | func (bl *blacklist) Add(addr krpc.Addr) { 141 | ip := addr.IP.String() 142 | bl.lock.Lock() 143 | wp, ok := bl.ips[ip] 144 | if !ok { 145 | if bl.num > 0 && len(bl.ips) >= bl.num { 146 | bl.lock.Unlock() 147 | return 148 | } 149 | 150 | wp = &wrappedPort{Enable: true} 151 | bl.ips[ip] = wp 152 | } 153 | 154 | if addr.Port < 1 { 155 | wp.Enable = false 156 | wp.Ports = nil 157 | } else if wp.Ports == nil { 158 | wp.Ports = map[uint16]struct{}{addr.Port: {}} 159 | } else { 160 | wp.Ports[addr.Port] = struct{}{} 161 | } 162 | 163 | wp.Time = time.Now() 164 | bl.lock.Unlock() 165 | } 166 | 167 | func (bl *blacklist) Del(addr krpc.Addr) { 168 | ip := addr.IP.String() 169 | bl.lock.Lock() 170 | if wp, ok := bl.ips[ip]; ok { 171 | if addr.Port < 1 { 172 | delete(bl.ips, ip) 173 | } else if wp.Enable { 174 | switch len(wp.Ports) { 175 | case 0, 1: 176 | delete(bl.ips, ip) 177 | default: 178 | delete(wp.Ports, addr.Port) 179 | wp.Time = time.Now() 180 | } 181 | } 182 | } 183 | bl.lock.Unlock() 184 | } 185 | -------------------------------------------------------------------------------- /dht/blacklist_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package dht 16 | 17 | import ( 18 | "net" 19 | "testing" 20 | "time" 21 | 22 | "github.com/xgfone/go-bt/krpc" 23 | ) 24 | 25 | func (bl *blacklist) portsLen() (n int) { 26 | bl.lock.RLock() 27 | for _, wp := range bl.ips { 28 | n += len(wp.Ports) 29 | } 30 | bl.lock.RUnlock() 31 | return 32 | } 33 | 34 | func (bl *blacklist) getIPs() []string { 35 | bl.lock.RLock() 36 | ips := make([]string, 0, len(bl.ips)) 37 | for ip := range bl.ips { 38 | ips = append(ips, ip) 39 | } 40 | bl.lock.RUnlock() 41 | return ips 42 | } 43 | 44 | func TestMemoryBlacklist(t *testing.T) { 45 | bl := NewMemoryBlacklist(3, time.Second).(*blacklist) 46 | defer bl.Close() 47 | 48 | bl.Add(krpc.NewAddr(net.ParseIP("1.1.1.1"), 123)) 49 | bl.Add(krpc.NewAddr(net.ParseIP("1.1.1.1"), 456)) 50 | bl.Add(krpc.NewAddr(net.ParseIP("1.1.1.1"), 789)) 51 | bl.Add(krpc.NewAddr(net.ParseIP("2.2.2.2"), 111)) 52 | bl.Add(krpc.NewAddr(net.ParseIP("3.3.3.3"), 0)) 53 | bl.Add(krpc.NewAddr(net.ParseIP("4.4.4.4"), 222)) 54 | 55 | ips := bl.getIPs() 56 | if len(ips) != 3 { 57 | t.Error(ips) 58 | } else { 59 | for _, ip := range ips { 60 | switch ip { 61 | case "1.1.1.1", "2.2.2.2", "3.3.3.3": 62 | default: 63 | t.Error(ip) 64 | } 65 | } 66 | } 67 | 68 | if n := bl.portsLen(); n != 4 { 69 | t.Errorf("expect port num 4, but got %d", n) 70 | } 71 | 72 | if bl.In(krpc.NewAddr(net.ParseIP("1.1.1.1"), 111)) || !bl.In(krpc.NewAddr(net.ParseIP("1.1.1.1"), 123)) { 73 | t.Fail() 74 | } 75 | if !bl.In(krpc.NewAddr(net.ParseIP("3.3.3.3"), 111)) || bl.In(krpc.NewAddr(net.ParseIP("4.4.4.4"), 222)) { 76 | t.Fail() 77 | } 78 | 79 | bl.Del(krpc.NewAddr(net.ParseIP("3.3.3.3"), 0)) 80 | if bl.In(krpc.NewAddr(net.ParseIP("3.3.3.3"), 111)) { 81 | t.Fail() 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /dht/dht_server_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package dht 16 | 17 | import ( 18 | "fmt" 19 | "net" 20 | "sync" 21 | "time" 22 | 23 | "github.com/xgfone/go-bt/internal/helper" 24 | "github.com/xgfone/go-bt/krpc" 25 | "github.com/xgfone/go-bt/metainfo" 26 | ) 27 | 28 | type testPeerManager struct { 29 | lock sync.RWMutex 30 | peers map[metainfo.Hash][]string 31 | } 32 | 33 | func newTestPeerManager() *testPeerManager { 34 | return &testPeerManager{peers: make(map[metainfo.Hash][]string)} 35 | } 36 | 37 | func (pm *testPeerManager) AddPeer(infohash metainfo.Hash, addr string) { 38 | pm.lock.Lock() 39 | defer pm.lock.Unlock() 40 | 41 | if !helper.ContainsString(pm.peers[infohash], addr) { 42 | pm.peers[infohash] = append(pm.peers[infohash], addr) 43 | } 44 | } 45 | 46 | func (pm *testPeerManager) GetPeers(infohash metainfo.Hash, maxnum int, ipv6 bool) (addrs []string) { 47 | // We only supports IPv4, so ignore the ipv6 argument. 48 | pm.lock.RLock() 49 | _addrs := pm.peers[infohash] 50 | if _len := len(_addrs); _len > 0 { 51 | if _len > maxnum { 52 | _len = maxnum 53 | } 54 | addrs = _addrs[:_len] 55 | } 56 | pm.lock.RUnlock() 57 | return 58 | } 59 | 60 | func onSearch(infohash string, addr krpc.Addr) { 61 | fmt.Printf("%s is searching %s\n", addr.String(), infohash) 62 | } 63 | 64 | func onTorrent(infohash string, addr string) { 65 | fmt.Printf("%s has downloaded %s\n", addr, infohash) 66 | } 67 | 68 | func newDHTServer(id metainfo.Hash, addr string, pm PeerManager) (s *Server, err error) { 69 | conn, err := net.ListenPacket("udp", addr) 70 | if err == nil { 71 | c := Config{ID: id, PeerManager: pm, OnSearch: onSearch, OnTorrent: onTorrent} 72 | s = NewServer(conn, &c) 73 | } 74 | return 75 | } 76 | 77 | func ExampleServer() { 78 | // For test, we predefine some node ids and infohash. 79 | id1 := metainfo.NewRandomHash() 80 | id2 := metainfo.NewRandomHash() 81 | id3 := metainfo.NewRandomHash() 82 | infohash := metainfo.Hash{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 83 | 11, 12, 13, 14, 15, 16, 17, 18, 19, 20} 84 | 85 | // Create first DHT server 86 | pm := newTestPeerManager() 87 | server1, err := newDHTServer(id1, "127.0.0.1:9001", pm) 88 | if err != nil { 89 | fmt.Println(err) 90 | return 91 | } 92 | defer server1.Close() 93 | 94 | // Create second DHT server 95 | server2, err := newDHTServer(id2, "127.0.0.1:9002", nil) 96 | if err != nil { 97 | fmt.Println(err) 98 | return 99 | } 100 | defer server2.Close() 101 | 102 | // Create third DHT server 103 | server3, err := newDHTServer(id3, "127.0.0.1:9003", nil) 104 | if err != nil { 105 | fmt.Println(err) 106 | return 107 | } 108 | defer server3.Close() 109 | 110 | // Start the three DHT servers 111 | go server1.Run() 112 | go server2.Run() 113 | go server3.Run() 114 | 115 | // Wait that the DHT servers start. 116 | time.Sleep(time.Second) 117 | 118 | // Bootstrap the routing table to create a DHT network with the three servers 119 | server1.Bootstrap([]string{"127.0.0.1:9002", "127.0.0.1:9003"}) 120 | server2.Bootstrap([]string{"127.0.0.1:9001", "127.0.0.1:9003"}) 121 | server3.Bootstrap([]string{"127.0.0.1:9001", "127.0.0.1:9002"}) 122 | 123 | // Wait that the DHT servers learn the routing table 124 | time.Sleep(time.Second) 125 | 126 | fmt.Println("Server1:", server1.Node4Num()) 127 | fmt.Println("Server2:", server2.Node4Num()) 128 | fmt.Println("Server3:", server3.Node4Num()) 129 | 130 | server1.GetPeers(infohash, func(r Result) { 131 | if len(r.Peers) == 0 { 132 | fmt.Printf("%s: no peers for %s\n", r.Addr.String(), infohash) 133 | } else { 134 | for _, peer := range r.Peers { 135 | fmt.Printf("%s: %s\n", infohash, peer) 136 | } 137 | } 138 | }) 139 | 140 | // Wait that the last get_peers ends. 141 | time.Sleep(time.Second * 2) 142 | 143 | // Add the peer to let the DHT server1 has the peer. 144 | pm.AddPeer(infohash, "127.0.0.1:9001") 145 | 146 | // Search the torrent infohash again, but from DHT server2, 147 | // which will search the DHT server1 recursively. 148 | server2.GetPeers(infohash, func(r Result) { 149 | if len(r.Peers) == 0 { 150 | fmt.Printf("%s: no peers for %s\n", r.Addr.String(), infohash) 151 | } else { 152 | for _, peer := range r.Peers { 153 | fmt.Printf("%s: %s\n", infohash, peer) 154 | } 155 | } 156 | }) 157 | 158 | // Wait that the recursive call ends. 159 | time.Sleep(time.Second * 2) 160 | 161 | // Unordered output: 162 | // Server1: 2 163 | // Server2: 2 164 | // Server3: 2 165 | // 127.0.0.1:9001 is searching 0102030405060708090a0b0c0d0e0f1011121314 166 | // 127.0.0.1:9001 is searching 0102030405060708090a0b0c0d0e0f1011121314 167 | // 127.0.0.1:9002: no peers for 0102030405060708090a0b0c0d0e0f1011121314 168 | // 127.0.0.1:9003: no peers for 0102030405060708090a0b0c0d0e0f1011121314 169 | // 127.0.0.1:9002 is searching 0102030405060708090a0b0c0d0e0f1011121314 170 | // 127.0.0.1:9002 is searching 0102030405060708090a0b0c0d0e0f1011121314 171 | // 127.0.0.1:9003: no peers for 0102030405060708090a0b0c0d0e0f1011121314 172 | // 0102030405060708090a0b0c0d0e0f1011121314: 127.0.0.1:9001 173 | // 127.0.0.1:9001 has downloaded 0102030405060708090a0b0c0d0e0f1011121314 174 | } 175 | -------------------------------------------------------------------------------- /dht/peer_manager.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package dht 16 | 17 | import ( 18 | "sync" 19 | "time" 20 | 21 | "github.com/xgfone/go-bt/krpc" 22 | "github.com/xgfone/go-bt/metainfo" 23 | ) 24 | 25 | // PeerManager is used to manage the peers. 26 | type PeerManager interface { 27 | // If ipv6 is true, only return ipv6 addresses. Or return ipv4 addresses. 28 | GetPeers(infohash metainfo.Hash, maxnum int, ipv6 bool) []string 29 | } 30 | 31 | var _ PeerManager = new(tokenPeerManager) 32 | 33 | type peer struct { 34 | ID metainfo.Hash 35 | Addr krpc.Addr 36 | Token string 37 | Time time.Time 38 | } 39 | 40 | type tokenPeerManager struct { 41 | lock sync.RWMutex 42 | exit chan struct{} 43 | peers map[metainfo.Hash]map[string]peer 44 | } 45 | 46 | func newTokenPeerManager() *tokenPeerManager { 47 | return &tokenPeerManager{ 48 | exit: make(chan struct{}), 49 | peers: make(map[metainfo.Hash]map[string]peer, 128), 50 | } 51 | } 52 | 53 | // Start starts the token-peer manager. 54 | func (tpm *tokenPeerManager) Start(interval time.Duration) { 55 | tick := time.NewTicker(interval) 56 | defer tick.Stop() 57 | for { 58 | select { 59 | case <-tpm.exit: 60 | return 61 | case now := <-tick.C: 62 | tpm.lock.Lock() 63 | for id, peers := range tpm.peers { 64 | for addr, peer := range peers { 65 | if now.Sub(peer.Time) > interval { 66 | delete(peers, addr) 67 | } 68 | } 69 | 70 | if len(peers) == 0 { 71 | delete(tpm.peers, id) 72 | } 73 | } 74 | tpm.lock.Unlock() 75 | } 76 | } 77 | } 78 | 79 | func (tpm *tokenPeerManager) Set(id metainfo.Hash, addr krpc.Addr, token string) { 80 | addrkey := addr.String() 81 | tpm.lock.Lock() 82 | peers, ok := tpm.peers[id] 83 | if !ok { 84 | peers = make(map[string]peer, 4) 85 | tpm.peers[id] = peers 86 | } 87 | peers[addrkey] = peer{ID: id, Addr: addr, Token: token, Time: time.Now()} 88 | tpm.lock.Unlock() 89 | } 90 | 91 | func (tpm *tokenPeerManager) Get(id metainfo.Hash, addr krpc.Addr) (token string) { 92 | addrkey := addr.String() 93 | tpm.lock.RLock() 94 | if peers, ok := tpm.peers[id]; ok { 95 | if peer, ok := peers[addrkey]; ok { 96 | token = peer.Token 97 | } 98 | } 99 | tpm.lock.RUnlock() 100 | return 101 | } 102 | 103 | func (tpm *tokenPeerManager) Stop() { 104 | select { 105 | case <-tpm.exit: 106 | default: 107 | close(tpm.exit) 108 | } 109 | } 110 | 111 | func (tpm *tokenPeerManager) GetPeers(infohash metainfo.Hash, maxnum int, ipv6 bool) (addrs []string) { 112 | addrs = make([]string, 0, maxnum) 113 | tpm.lock.RLock() 114 | if peers, ok := tpm.peers[infohash]; ok { 115 | for _, peer := range peers { 116 | if maxnum < 1 { 117 | break 118 | } 119 | 120 | if ipv6 { // For IPv6 121 | if isIPv6(peer.Addr.IP) { 122 | maxnum-- 123 | addrs = append(addrs, peer.Addr.String()) 124 | } 125 | } else if !isIPv6(peer.Addr.IP) { // For IPv4 126 | maxnum-- 127 | addrs = append(addrs, peer.Addr.String()) 128 | } 129 | } 130 | } 131 | tpm.lock.RUnlock() 132 | return 133 | } 134 | -------------------------------------------------------------------------------- /dht/routing_table_storage.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package dht 16 | 17 | import ( 18 | "time" 19 | 20 | "github.com/xgfone/go-bt/krpc" 21 | "github.com/xgfone/go-bt/metainfo" 22 | ) 23 | 24 | // RoutingTableNode represents the node with last changed time in the routing table. 25 | type RoutingTableNode struct { 26 | Node krpc.Node 27 | LastChanged time.Time 28 | } 29 | 30 | // RoutingTableStorage is used to store the nodes in the routing table. 31 | type RoutingTableStorage interface { 32 | Load(ownid metainfo.Hash, ipv6 bool) (nodes []RoutingTableNode, err error) 33 | Dump(ownid metainfo.Hash, nodes []RoutingTableNode, ipv6 bool) (err error) 34 | } 35 | 36 | // NewNoopRoutingTableStorage returns a no-op RoutingTableStorage. 37 | func NewNoopRoutingTableStorage() RoutingTableStorage { return noopStorage{} } 38 | 39 | type noopStorage struct{} 40 | 41 | func (s noopStorage) Load(metainfo.Hash, bool) (nodes []RoutingTableNode, err error) { return } 42 | func (s noopStorage) Dump(metainfo.Hash, []RoutingTableNode, bool) (err error) { return } 43 | -------------------------------------------------------------------------------- /dht/token_manager.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package dht 16 | 17 | import ( 18 | "sync" 19 | "time" 20 | 21 | "github.com/xgfone/go-bt/internal/helper" 22 | "github.com/xgfone/go-bt/krpc" 23 | ) 24 | 25 | // TokenManager is used to manage and validate the token. 26 | // 27 | // (xgf): Should we allocate the different token for each node?? 28 | type tokenManager struct { 29 | lock sync.RWMutex 30 | last string 31 | new string 32 | exit chan struct{} 33 | addrs sync.Map 34 | } 35 | 36 | func newTokenManager() *tokenManager { 37 | token := helper.RandomString(8) 38 | return &tokenManager{last: token, new: token, exit: make(chan struct{})} 39 | } 40 | 41 | func (tm *tokenManager) updateToken() { 42 | token := helper.RandomString(8) 43 | tm.lock.Lock() 44 | tm.last, tm.new = tm.new, token 45 | tm.lock.Unlock() 46 | } 47 | 48 | func (tm *tokenManager) clear(now time.Time, expired time.Duration) { 49 | tm.addrs.Range(func(k interface{}, v interface{}) bool { 50 | if now.Sub(v.(time.Time)) >= expired { 51 | tm.addrs.Delete(k) 52 | } 53 | return true 54 | }) 55 | } 56 | 57 | // Start starts the token manager. 58 | func (tm *tokenManager) Start(expired time.Duration) { 59 | tick1 := time.NewTicker(expired) 60 | tick2 := time.NewTicker(expired / 2) 61 | defer tick1.Stop() 62 | defer tick2.Stop() 63 | 64 | for { 65 | select { 66 | case <-tm.exit: 67 | return 68 | case <-tick2.C: 69 | tm.updateToken() 70 | case now := <-tick1.C: 71 | tm.clear(now, expired) 72 | } 73 | } 74 | } 75 | 76 | // Stop stops the token manager. 77 | func (tm *tokenManager) Stop() { 78 | select { 79 | case <-tm.exit: 80 | default: 81 | close(tm.exit) 82 | } 83 | } 84 | 85 | // Token allocates a token for a node addr and returns the token. 86 | func (tm *tokenManager) Token(addr krpc.Addr) (token string) { 87 | addrs := addr.String() 88 | tm.lock.RLock() 89 | token = tm.new 90 | tm.lock.RUnlock() 91 | tm.addrs.Store(addrs, time.Now()) 92 | return 93 | } 94 | 95 | // Check checks whether the token associated with the node addr is valid, 96 | // that's, it's not expired. 97 | func (tm *tokenManager) Check(addr krpc.Addr, token string) (ok bool) { 98 | tm.lock.RLock() 99 | last, new := tm.last, tm.new 100 | tm.lock.RUnlock() 101 | 102 | if last == token || new == token { 103 | _, ok = tm.addrs.Load(addr.String()) 104 | } 105 | 106 | return 107 | } 108 | -------------------------------------------------------------------------------- /dht/transaction_manager.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package dht 16 | 17 | import ( 18 | "strconv" 19 | "sync" 20 | "sync/atomic" 21 | "time" 22 | 23 | "github.com/xgfone/go-bt/krpc" 24 | "github.com/xgfone/go-bt/metainfo" 25 | ) 26 | 27 | type transaction struct { 28 | ID string 29 | Query string 30 | Arg krpc.QueryArg 31 | Addr krpc.Addr 32 | Time time.Time 33 | Depth int 34 | 35 | Visited metainfo.Hashes 36 | Callback func(Result) 37 | OnError func(t *transaction, code int, reason string) 38 | OnTimeout func(t *transaction) 39 | OnResponse func(t *transaction, radd krpc.Addr, msg krpc.Message) 40 | } 41 | 42 | func (t *transaction) Done(r Result) { 43 | if t.Callback != nil { 44 | r.Addr = t.Addr 45 | t.Callback(r) 46 | } 47 | } 48 | 49 | func noopResponse(*transaction, krpc.Addr, krpc.Message) {} 50 | func newTransaction(s *Server, a krpc.Addr, q string, qa krpc.QueryArg, 51 | callback ...func(Result)) *transaction { 52 | var cb func(Result) 53 | if len(callback) > 0 { 54 | cb = callback[0] 55 | } 56 | 57 | return &transaction{ 58 | Addr: a, 59 | Query: q, 60 | Arg: qa, 61 | Callback: cb, 62 | OnError: s.onError, 63 | OnTimeout: s.onTimeout, 64 | OnResponse: noopResponse, 65 | Time: time.Now(), 66 | } 67 | } 68 | 69 | type transactionkey struct { 70 | id string 71 | addr string 72 | } 73 | 74 | type transactionManager struct { 75 | lock sync.Mutex 76 | exit chan struct{} 77 | trans map[transactionkey]*transaction 78 | tid uint32 79 | } 80 | 81 | func newTransactionManager() *transactionManager { 82 | return &transactionManager{ 83 | exit: make(chan struct{}), 84 | trans: make(map[transactionkey]*transaction, 128), 85 | } 86 | } 87 | 88 | // Start starts the transaction manager. 89 | func (tm *transactionManager) Start(s *Server, interval time.Duration) { 90 | tick := time.NewTicker(interval) 91 | defer tick.Stop() 92 | for { 93 | select { 94 | case <-tm.exit: 95 | return 96 | case now := <-tick.C: 97 | tm.lock.Lock() 98 | for k, t := range tm.trans { 99 | if now.Sub(t.Time) > interval { 100 | delete(tm.trans, k) 101 | t.OnTimeout(t) 102 | } 103 | } 104 | tm.lock.Unlock() 105 | } 106 | } 107 | } 108 | 109 | // Stop stops the transaction manager. 110 | func (tm *transactionManager) Stop() { 111 | select { 112 | case <-tm.exit: 113 | default: 114 | close(tm.exit) 115 | } 116 | } 117 | 118 | // GetTransactionID returns a new transaction id. 119 | func (tm *transactionManager) GetTransactionID() string { 120 | return strconv.FormatUint(uint64(atomic.AddUint32(&tm.tid, 1)), 36) 121 | } 122 | 123 | // AddTransaction adds the new transaction. 124 | func (tm *transactionManager) AddTransaction(t *transaction) { 125 | key := transactionkey{id: t.ID, addr: t.Addr.String()} 126 | tm.lock.Lock() 127 | tm.trans[key] = t 128 | tm.lock.Unlock() 129 | } 130 | 131 | // DeleteTransaction deletes the transaction. 132 | func (tm *transactionManager) DeleteTransaction(t *transaction) { 133 | key := transactionkey{id: t.ID, addr: t.Addr.String()} 134 | tm.lock.Lock() 135 | delete(tm.trans, key) 136 | tm.lock.Unlock() 137 | } 138 | 139 | // PopTransaction deletes and returns the transaction by the transaction id 140 | // and the peer address. 141 | // 142 | // Return nil if there is no the transaction. 143 | func (tm *transactionManager) PopTransaction(tid string, addr krpc.Addr) (t *transaction) { 144 | key := transactionkey{id: tid, addr: addr.String()} 145 | tm.lock.Lock() 146 | if t = tm.trans[key]; t != nil { 147 | delete(tm.trans, key) 148 | } 149 | tm.lock.Unlock() 150 | return 151 | } 152 | -------------------------------------------------------------------------------- /downloader/block_handler.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package downloader 16 | 17 | import pp "github.com/xgfone/go-bt/peerprotocol" 18 | 19 | // BlockDownloadHandler is used to downloads the files in the torrent file. 20 | type BlockDownloadHandler struct { 21 | pp.NoopHandler 22 | pp.NoopBep3Handler 23 | pp.NoopBep6Handler 24 | 25 | OnBlock func(index, offset uint32, b []byte) error 26 | ReqBlock func(c *pp.PeerConn) error 27 | PieceNum int 28 | } 29 | 30 | // NewBlockDownloadHandler returns a new BlockDownloadHandler. 31 | func NewBlockDownloadHandler(pieceNum int, reqBlock func(c *pp.PeerConn) error, 32 | onBlock func(pieceIndex, pieceOffset uint32, b []byte) error) BlockDownloadHandler { 33 | return BlockDownloadHandler{ 34 | OnBlock: onBlock, 35 | ReqBlock: reqBlock, 36 | PieceNum: pieceNum, 37 | } 38 | } 39 | 40 | /// --------------------------------------------------------------------------- 41 | /// BEP 3 42 | 43 | func (fd BlockDownloadHandler) request(pc *pp.PeerConn) (err error) { 44 | if fd.ReqBlock == nil { 45 | return nil 46 | } 47 | 48 | if pc.PeerChoked { 49 | err = pp.ErrChoked 50 | } else { 51 | err = fd.ReqBlock(pc) 52 | } 53 | return 54 | } 55 | 56 | // Piece implements the interface Bep3Handler#Piece. 57 | func (fd BlockDownloadHandler) Piece(c *pp.PeerConn, i, b uint32, p []byte) error { 58 | if fd.OnBlock != nil { 59 | if err := fd.OnBlock(i, b, p); err != nil { 60 | return err 61 | } 62 | } 63 | return fd.request(c) 64 | } 65 | 66 | // Unchoke implements the interface Bep3Handler#Unchoke. 67 | func (fd BlockDownloadHandler) Unchoke(pc *pp.PeerConn) (err error) { 68 | return fd.request(pc) 69 | } 70 | 71 | // Have implements the interface Bep3Handler#Have. 72 | func (fd BlockDownloadHandler) Have(pc *pp.PeerConn, index uint32) (err error) { 73 | pc.BitField.Set(index) 74 | return 75 | } 76 | 77 | /// --------------------------------------------------------------------------- 78 | /// BEP 6 79 | 80 | // HaveAll implements the interface Bep6Handler#HaveAll. 81 | func (fd BlockDownloadHandler) HaveAll(pc *pp.PeerConn) (err error) { 82 | if fd.PieceNum > 0 { 83 | pc.BitField = pp.NewBitField(fd.PieceNum, true) 84 | } 85 | return 86 | } 87 | 88 | // Reject implements the interface Bep6Handler#Reject. 89 | func (fd BlockDownloadHandler) Reject(pc *pp.PeerConn, index, begin, length uint32) (err error) { 90 | pc.BitField.Unset(index) 91 | return fd.request(pc) 92 | } 93 | -------------------------------------------------------------------------------- /downloader/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package downloader is used to download the torrent or the real file 16 | // from the peer node by the peer wire protocol. 17 | package downloader 18 | -------------------------------------------------------------------------------- /downloader/torrent_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package downloader 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | "fmt" 21 | "time" 22 | 23 | "github.com/xgfone/go-bt/metainfo" 24 | "github.com/xgfone/go-bt/peerprotocol" 25 | pp "github.com/xgfone/go-bt/peerprotocol" 26 | ) 27 | 28 | type bep10Handler struct { 29 | peerprotocol.NoopHandler // For implementing peerprotocol.Handler. 30 | 31 | infodata string 32 | } 33 | 34 | func (h bep10Handler) OnExtHandShake(c *peerprotocol.PeerConn) error { 35 | if _, ok := c.ExtendedHandshakeMsg.M[peerprotocol.ExtendedMessageNameMetadata]; !ok { 36 | return errors.New("missing the extension 'ut_metadata'") 37 | } 38 | 39 | return c.SendExtHandshakeMsg(peerprotocol.ExtendedHandshakeMsg{ 40 | M: map[string]uint8{pp.ExtendedMessageNameMetadata: 2}, 41 | MetadataSize: uint64(len(h.infodata)), 42 | }) 43 | } 44 | 45 | func (h bep10Handler) OnPayload(c *peerprotocol.PeerConn, extid uint8, extdata []byte) error { 46 | if extid != 2 { 47 | return fmt.Errorf("unknown extension id %d", extid) 48 | } 49 | 50 | var reqmsg peerprotocol.UtMetadataExtendedMsg 51 | if err := reqmsg.DecodeFromPayload(extdata); err != nil { 52 | return err 53 | } 54 | 55 | if reqmsg.MsgType != peerprotocol.UtMetadataExtendedMsgTypeRequest { 56 | return errors.New("unsupported ut_metadata extension type") 57 | } 58 | 59 | startIndex := reqmsg.Piece * BlockSize 60 | endIndex := startIndex + BlockSize 61 | if totalSize := len(h.infodata); totalSize < endIndex { 62 | endIndex = totalSize 63 | } 64 | 65 | respmsg := peerprotocol.UtMetadataExtendedMsg{ 66 | MsgType: peerprotocol.UtMetadataExtendedMsgTypeData, 67 | Piece: reqmsg.Piece, 68 | Data: []byte(h.infodata[startIndex:endIndex]), 69 | } 70 | 71 | data, err := respmsg.EncodeToBytes() 72 | if err != nil { 73 | return err 74 | } 75 | 76 | peerextid := c.ExtendedHandshakeMsg.M[peerprotocol.ExtendedMessageNameMetadata] 77 | return c.SendExtMsg(peerextid, data) 78 | } 79 | 80 | func ExampleTorrentDownloader() { 81 | ctx, cancel := context.WithCancel(context.Background()) 82 | defer cancel() 83 | 84 | handler := bep10Handler{infodata: "1234567890"} 85 | infohash := metainfo.NewHashFromString(handler.infodata) 86 | 87 | // Start the torrent server. 88 | var serverConfig peerprotocol.Config 89 | serverConfig.ExtBits.Set(peerprotocol.ExtensionBitExtended) 90 | server, err := peerprotocol.NewServerByListen("tcp", "127.0.0.1:9010", metainfo.NewRandomHash(), handler, &serverConfig) 91 | if err != nil { 92 | fmt.Println(err) 93 | return 94 | } 95 | defer server.Close() 96 | 97 | go server.Run() // Start the torrent server. 98 | time.Sleep(time.Millisecond * 100) // Wait that the torrent server finishes to start. 99 | 100 | // Start the torrent downloader. 101 | downloaderConfig := &TorrentDownloaderConfig{WorkerNum: 3, DialTimeout: time.Second} 102 | downloader := NewTorrentDownloader(downloaderConfig) 103 | go func() { 104 | for { 105 | select { 106 | case <-ctx.Done(): 107 | return 108 | case result := <-downloader.Response(): 109 | fmt.Println(string(result.InfoBytes)) 110 | } 111 | } 112 | }() 113 | 114 | // Start to download the torrent. 115 | downloader.Request("127.0.0.1", 9010, infohash) 116 | 117 | // Wait to finish the test. 118 | time.Sleep(time.Second) 119 | cancel() 120 | time.Sleep(time.Millisecond * 50) 121 | 122 | // Output: 123 | // 1234567890 124 | } 125 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/xgfone/go-bt 2 | 3 | go 1.11 4 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xgfone/go-bt/aa4abbc304d268e933902e66cf7645a4ceeb306a/go.sum -------------------------------------------------------------------------------- /internal/helper/io.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package helper 16 | 17 | import "io" 18 | 19 | // CopyNBuffer is the same as io.CopyN, but uses the given buf as the buffer. 20 | func CopyNBuffer(dst io.Writer, src io.Reader, n int64, buf []byte) (written int64, err error) { 21 | written, err = io.CopyBuffer(dst, io.LimitReader(src, n), buf) 22 | if written == n { 23 | return n, nil 24 | } else if written < n && err == nil { 25 | // src stopped early; must have been EOF. 26 | err = io.EOF 27 | } 28 | 29 | return 30 | } 31 | -------------------------------------------------------------------------------- /internal/helper/slice.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package helper 16 | 17 | // ContainsString reports whether s is in ss. 18 | func ContainsString(ss []string, s string) bool { 19 | for _, v := range ss { 20 | if v == s { 21 | return true 22 | } 23 | } 24 | return false 25 | } 26 | -------------------------------------------------------------------------------- /internal/helper/slice_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package helper 16 | 17 | import "testing" 18 | 19 | func TestContainsString(t *testing.T) { 20 | if !ContainsString([]string{"a", "b"}, "a") { 21 | t.Fail() 22 | } 23 | 24 | if ContainsString([]string{"a", "b"}, "z") { 25 | t.Fail() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /internal/helper/string.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package helper 16 | 17 | import ( 18 | crand "crypto/rand" 19 | "math/rand" 20 | ) 21 | 22 | // RandomString generates a size-length string randomly. 23 | func RandomString(size int) string { 24 | bs := make([]byte, size) 25 | if n, _ := crand.Read(bs); n < size { 26 | for ; n < size; n++ { 27 | bs[n] = byte(rand.Intn(256)) 28 | } 29 | } 30 | return string(bs) 31 | } 32 | -------------------------------------------------------------------------------- /krpc/addr_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package krpc 16 | 17 | import ( 18 | "bytes" 19 | "net" 20 | "testing" 21 | 22 | "github.com/xgfone/go-bt/bencode" 23 | ) 24 | 25 | func TestAddr(t *testing.T) { 26 | addrs := []Addr{ 27 | {IP: net.ParseIP("172.16.1.1").To4(), Port: 123}, 28 | {IP: net.ParseIP("192.168.1.1").To4(), Port: 456}, 29 | } 30 | expect := "l6:\xac\x10\x01\x01\x00\x7b6:\xc0\xa8\x01\x01\x01\xc8e" 31 | 32 | buf := new(bytes.Buffer) 33 | if err := bencode.NewEncoder(buf).Encode(addrs); err != nil { 34 | t.Error(err) 35 | } else if result := buf.String(); result != expect { 36 | t.Errorf("expect %s, but got %x\n", expect, result) 37 | } 38 | 39 | var raddrs []Addr 40 | if err := bencode.DecodeString(expect, &raddrs); err != nil { 41 | t.Error(err) 42 | } else if len(raddrs) != len(addrs) { 43 | t.Errorf("expect addrs length %d, but got %d\n", len(addrs), len(raddrs)) 44 | } else { 45 | for i, addr := range addrs { 46 | if !addr.Equal(raddrs[i]) { 47 | t.Errorf("%d: expect addr %v, but got %v\n", i, addr, raddrs[i]) 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /krpc/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package krpc supplies the KRPC message used by DHT. 16 | package krpc 17 | -------------------------------------------------------------------------------- /krpc/message_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package krpc 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/xgfone/go-bt/bencode" 21 | ) 22 | 23 | func TestMessage(t *testing.T) { 24 | s, err := bencode.EncodeString(Message{RO: true}) 25 | if err != nil { 26 | t.Fatal(err) 27 | } 28 | 29 | var ms map[string]interface{} 30 | if err := bencode.DecodeString(s, &ms); err != nil { 31 | t.Fatal(err) 32 | } else if len(ms) != 3 { 33 | t.Fatal() 34 | } else if v, ok := ms["t"]; !ok || v != "" { 35 | t.Fatal() 36 | } else if v, ok := ms["y"]; !ok || v != "" { 37 | t.Fatal() 38 | } else if v, ok := ms["ro"].(int64); !ok || v != 1 { 39 | t.Fatal() 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /krpc/node.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020~2023 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package krpc 16 | 17 | import ( 18 | "bytes" 19 | "encoding" 20 | "fmt" 21 | "io" 22 | 23 | "github.com/xgfone/go-bt/bencode" 24 | "github.com/xgfone/go-bt/metainfo" 25 | ) 26 | 27 | // Node represents a node information. 28 | type Node struct { 29 | ID metainfo.Hash 30 | Addr Addr 31 | } 32 | 33 | // NewNode returns a new Node. 34 | func NewNode(id metainfo.Hash, addr Addr) Node { 35 | return Node{ID: id, Addr: addr} 36 | } 37 | 38 | func (n Node) String() string { 39 | return fmt.Sprintf("Node<%x@%s>", n.ID, n.Addr) 40 | } 41 | 42 | // Equal reports whether n is equal to o. 43 | func (n Node) Equal(o Node) bool { 44 | return n.ID == o.ID && n.Addr.Equal(o.Addr) 45 | } 46 | 47 | // WriteBinary is the same as MarshalBinary, but writes the result into w 48 | // instead of returning. 49 | func (n Node) WriteBinary(w io.Writer) (m int, err error) { 50 | var n1, n2 int 51 | if n1, err = w.Write(n.ID[:]); err == nil { 52 | m = n1 53 | if n2, err = n.Addr.WriteBinary(w); err == nil { 54 | m += n2 55 | } 56 | } 57 | return 58 | } 59 | 60 | var ( 61 | _ encoding.BinaryMarshaler = new(Node) 62 | _ encoding.BinaryUnmarshaler = new(Node) 63 | ) 64 | 65 | // MarshalBinary implements the interface encoding.BinaryMarshaler, 66 | // which implements "Compact node info". 67 | // 68 | // See http://bittorrent.org/beps/bep_0005.html. 69 | func (n Node) MarshalBinary() (data []byte, err error) { 70 | buf := bytes.NewBuffer(nil) 71 | buf.Grow(40) 72 | if _, err = n.WriteBinary(buf); err == nil { 73 | data = buf.Bytes() 74 | } 75 | return 76 | } 77 | 78 | // UnmarshalBinary implements the interface encoding.BinaryUnmarshaler, 79 | // which implements "Compact node info". 80 | // 81 | // See http://bittorrent.org/beps/bep_0005.html. 82 | func (n *Node) UnmarshalBinary(b []byte) error { 83 | if len(b) < 26 { 84 | return io.ErrShortBuffer 85 | } 86 | 87 | copy(n.ID[:], b[:20]) 88 | return n.Addr.UnmarshalBinary(b[20:]) 89 | } 90 | 91 | // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 92 | 93 | var ( 94 | _ bencode.Marshaler = new(CompactIPv4Nodes) 95 | _ bencode.Unmarshaler = new(CompactIPv4Nodes) 96 | _ encoding.BinaryMarshaler = new(CompactIPv4Nodes) 97 | _ encoding.BinaryUnmarshaler = new(CompactIPv4Nodes) 98 | ) 99 | 100 | // CompactIPv4Nodes is a set of IPv4 Nodes. 101 | type CompactIPv4Nodes []Node 102 | 103 | // MarshalBinary implements the interface encoding.BinaryMarshaler. 104 | func (cns CompactIPv4Nodes) MarshalBinary() ([]byte, error) { 105 | buf := bytes.NewBuffer(nil) 106 | buf.Grow(26 * len(cns)) 107 | for _, ni := range cns { 108 | if ni.Addr.IP = ni.Addr.IP.To4(); len(ni.Addr.IP) == 0 { 109 | continue 110 | } 111 | if n, err := ni.WriteBinary(buf); err != nil { 112 | return nil, err 113 | } else if n != 26 { 114 | panic(fmt.Errorf("CompactIPv4Nodes: the invalid node info length '%d'", n)) 115 | } 116 | } 117 | return buf.Bytes(), nil 118 | } 119 | 120 | // UnmarshalBinary implements the interface encoding.BinaryUnmarshaler. 121 | func (cns *CompactIPv4Nodes) UnmarshalBinary(b []byte) (err error) { 122 | _len := len(b) 123 | if _len%26 != 0 { 124 | return fmt.Errorf("CompactIPv4Nodes: invalid node info length '%d'", _len) 125 | } 126 | 127 | nis := make([]Node, 0, _len/26) 128 | for i := 0; i < _len; i += 26 { 129 | var ni Node 130 | if err = ni.UnmarshalBinary(b[i : i+26]); err != nil { 131 | return 132 | } 133 | nis = append(nis, ni) 134 | } 135 | 136 | *cns = nis 137 | return 138 | } 139 | 140 | // MarshalBencode implements the interface bencode.Marshaler. 141 | func (cns CompactIPv4Nodes) MarshalBencode() (b []byte, err error) { 142 | if b, err = cns.MarshalBinary(); err == nil { 143 | b, err = bencode.EncodeBytes(b) 144 | } 145 | return 146 | } 147 | 148 | // UnmarshalBencode implements the interface bencode.Unmarshaler. 149 | func (cns *CompactIPv4Nodes) UnmarshalBencode(b []byte) (err error) { 150 | var data []byte 151 | if err = bencode.DecodeBytes(b, &data); err == nil { 152 | err = cns.UnmarshalBinary(data) 153 | } 154 | return 155 | } 156 | 157 | // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 158 | 159 | var ( 160 | _ bencode.Marshaler = new(CompactIPv6Nodes) 161 | _ bencode.Unmarshaler = new(CompactIPv6Nodes) 162 | _ encoding.BinaryMarshaler = new(CompactIPv6Nodes) 163 | _ encoding.BinaryUnmarshaler = new(CompactIPv6Nodes) 164 | ) 165 | 166 | // CompactIPv6Nodes is a set of IPv6 Nodes. 167 | type CompactIPv6Nodes []Node 168 | 169 | // MarshalBinary implements the interface encoding.BinaryMarshaler. 170 | func (cns CompactIPv6Nodes) MarshalBinary() ([]byte, error) { 171 | buf := bytes.NewBuffer(nil) 172 | buf.Grow(38 * len(cns)) 173 | for _, ni := range cns { 174 | ni.Addr.IP = ni.Addr.IP.To16() 175 | if n, err := ni.WriteBinary(buf); err != nil { 176 | return nil, err 177 | } else if n != 38 { 178 | panic(fmt.Errorf("CompactIPv6Nodes: the invalid node info length '%d'", n)) 179 | } 180 | } 181 | return buf.Bytes(), nil 182 | } 183 | 184 | // UnmarshalBinary implements the interface encoding.BinaryUnmarshaler. 185 | func (cns *CompactIPv6Nodes) UnmarshalBinary(b []byte) (err error) { 186 | _len := len(b) 187 | if _len%38 != 0 { 188 | return fmt.Errorf("CompactIPv6Nodes: invalid node info length '%d'", _len) 189 | } 190 | 191 | nis := make([]Node, 0, _len/38) 192 | for i := 0; i < _len; i += 38 { 193 | var ni Node 194 | if err = ni.UnmarshalBinary(b[i : i+38]); err != nil { 195 | return 196 | } 197 | nis = append(nis, ni) 198 | } 199 | 200 | *cns = nis 201 | return 202 | } 203 | 204 | // MarshalBencode implements the interface bencode.Marshaler. 205 | func (cns CompactIPv6Nodes) MarshalBencode() (b []byte, err error) { 206 | if b, err = cns.MarshalBinary(); err == nil { 207 | b, err = bencode.EncodeBytes(b) 208 | } 209 | return 210 | } 211 | 212 | // UnmarshalBencode implements the interface bencode.Unmarshaler. 213 | func (cns *CompactIPv6Nodes) UnmarshalBencode(b []byte) (err error) { 214 | var data []byte 215 | if err = bencode.DecodeBytes(b, &data); err == nil { 216 | err = cns.UnmarshalBinary(data) 217 | } 218 | return 219 | } 220 | -------------------------------------------------------------------------------- /metainfo/addr_compact.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package metainfo 16 | 17 | import ( 18 | "bytes" 19 | "encoding" 20 | "encoding/binary" 21 | "errors" 22 | "fmt" 23 | "io" 24 | "net" 25 | "strconv" 26 | 27 | "github.com/xgfone/go-bt/bencode" 28 | ) 29 | 30 | // CompactAddr represents an address based on ip and port, 31 | // which implements "Compact IP-address/port info". 32 | // 33 | // See http://bittorrent.org/beps/bep_0005.html. 34 | type CompactAddr struct { 35 | IP net.IP // For IPv4, its length must be 4. 36 | Port uint16 37 | } 38 | 39 | // NewCompactAddr returns a new compact Addr with ip and port. 40 | func NewCompactAddr(ip net.IP, port uint16) CompactAddr { 41 | return CompactAddr{IP: ip, Port: port} 42 | } 43 | 44 | // NewCompactAddrFromUDPAddr converts *net.UDPAddr to a new CompactAddr. 45 | func NewCompactAddrFromUDPAddr(ua *net.UDPAddr) CompactAddr { 46 | return CompactAddr{IP: ua.IP, Port: uint16(ua.Port)} 47 | } 48 | 49 | // Valid reports whether the addr is valid. 50 | func (a CompactAddr) Valid() bool { 51 | return len(a.IP) > 0 && a.Port > 0 52 | } 53 | 54 | // Equal reports whether a is equal to o. 55 | func (a CompactAddr) Equal(o CompactAddr) bool { 56 | return a.Port == o.Port && a.IP.Equal(o.IP) 57 | } 58 | 59 | // UDPAddr converts itself to *net.Addr. 60 | func (a CompactAddr) UDPAddr() *net.UDPAddr { 61 | return &net.UDPAddr{IP: a.IP, Port: int(a.Port)} 62 | } 63 | 64 | var _ net.Addr = CompactAddr{} 65 | 66 | // Network implements the interface net.Addr#Network. 67 | func (a CompactAddr) Network() string { 68 | return "krpc" 69 | } 70 | 71 | func (a CompactAddr) String() string { 72 | if a.Port == 0 { 73 | return a.IP.String() 74 | } 75 | return net.JoinHostPort(a.IP.String(), strconv.FormatUint(uint64(a.Port), 10)) 76 | } 77 | 78 | // WriteBinary is the same as MarshalBinary, but writes the result into w 79 | // instead of returning. 80 | func (a CompactAddr) WriteBinary(w io.Writer) (n int, err error) { 81 | if n, err = w.Write(a.IP); err == nil { 82 | if err = binary.Write(w, binary.BigEndian, a.Port); err == nil { 83 | n += 2 84 | } 85 | } 86 | return 87 | } 88 | 89 | var ( 90 | _ encoding.BinaryMarshaler = new(CompactAddr) 91 | _ encoding.BinaryUnmarshaler = new(CompactAddr) 92 | ) 93 | 94 | // MarshalBinary implements the interface encoding.BinaryMarshaler, 95 | func (a CompactAddr) MarshalBinary() (data []byte, err error) { 96 | buf := bytes.NewBuffer(nil) 97 | buf.Grow(18) 98 | if _, err = a.WriteBinary(buf); err == nil { 99 | data = buf.Bytes() 100 | } 101 | return 102 | } 103 | 104 | // UnmarshalBinary implements the interface encoding.BinaryUnmarshaler. 105 | func (a *CompactAddr) UnmarshalBinary(data []byte) error { 106 | _len := len(data) - 2 107 | switch _len { 108 | case net.IPv4len, net.IPv6len: 109 | default: 110 | return errors.New("invalid compact ip-address/port info") 111 | } 112 | 113 | a.IP = make(net.IP, _len) 114 | copy(a.IP, data[:_len]) 115 | a.Port = binary.BigEndian.Uint16(data[_len:]) 116 | return nil 117 | } 118 | 119 | var ( 120 | _ bencode.Marshaler = new(CompactAddr) 121 | _ bencode.Unmarshaler = new(CompactAddr) 122 | ) 123 | 124 | // MarshalBencode implements the interface bencode.Marshaler. 125 | func (a CompactAddr) MarshalBencode() (b []byte, err error) { 126 | if b, err = a.MarshalBinary(); err == nil { 127 | b, err = bencode.EncodeBytes(b) 128 | } 129 | return 130 | } 131 | 132 | // UnmarshalBencode implements the interface bencode.Unmarshaler. 133 | func (a *CompactAddr) UnmarshalBencode(b []byte) (err error) { 134 | var data []byte 135 | if err = bencode.DecodeBytes(b, &data); err == nil { 136 | err = a.UnmarshalBinary(data) 137 | } 138 | return 139 | } 140 | 141 | // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 142 | 143 | var ( 144 | _ bencode.Marshaler = new(CompactIPv4Addrs) 145 | _ bencode.Unmarshaler = new(CompactIPv4Addrs) 146 | _ encoding.BinaryMarshaler = new(CompactIPv4Addrs) 147 | _ encoding.BinaryUnmarshaler = new(CompactIPv4Addrs) 148 | ) 149 | 150 | // CompactIPv4Addrs is a set of IPv4 Addrs. 151 | type CompactIPv4Addrs []CompactAddr 152 | 153 | // MarshalBinary implements the interface encoding.BinaryMarshaler. 154 | func (cas CompactIPv4Addrs) MarshalBinary() ([]byte, error) { 155 | buf := bytes.NewBuffer(nil) 156 | buf.Grow(6 * len(cas)) 157 | for _, addr := range cas { 158 | if addr.IP = addr.IP.To4(); len(addr.IP) == 0 { 159 | continue 160 | } 161 | 162 | if n, err := addr.WriteBinary(buf); err != nil { 163 | return nil, err 164 | } else if n != 6 { 165 | panic(fmt.Errorf("CompactIPv4Nodes: the invalid node info length '%d'", n)) 166 | } 167 | } 168 | return buf.Bytes(), nil 169 | } 170 | 171 | // UnmarshalBinary implements the interface encoding.BinaryUnmarshaler. 172 | func (cas *CompactIPv4Addrs) UnmarshalBinary(b []byte) (err error) { 173 | _len := len(b) 174 | if _len%6 != 0 { 175 | return fmt.Errorf("CompactIPv4Addrs: invalid addr info length '%d'", _len) 176 | } 177 | 178 | addrs := make(CompactIPv4Addrs, 0, _len/6) 179 | for i := 0; i < _len; i += 6 { 180 | var addr CompactAddr 181 | if err = addr.UnmarshalBinary(b[i : i+6]); err != nil { 182 | return 183 | } 184 | addrs = append(addrs, addr) 185 | } 186 | 187 | *cas = addrs 188 | return 189 | } 190 | 191 | // MarshalBencode implements the interface bencode.Marshaler. 192 | func (cas CompactIPv4Addrs) MarshalBencode() (b []byte, err error) { 193 | if b, err = cas.MarshalBinary(); err == nil { 194 | b, err = bencode.EncodeBytes(b) 195 | } 196 | return 197 | } 198 | 199 | // UnmarshalBencode implements the interface bencode.Unmarshaler. 200 | func (cas *CompactIPv4Addrs) UnmarshalBencode(b []byte) (err error) { 201 | var data []byte 202 | if err = bencode.DecodeBytes(b, &data); err == nil { 203 | err = cas.UnmarshalBinary(data) 204 | } 205 | return 206 | } 207 | 208 | // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 209 | 210 | var ( 211 | _ bencode.Marshaler = new(CompactIPv6Addrs) 212 | _ bencode.Unmarshaler = new(CompactIPv6Addrs) 213 | _ encoding.BinaryMarshaler = new(CompactIPv6Addrs) 214 | _ encoding.BinaryUnmarshaler = new(CompactIPv6Addrs) 215 | ) 216 | 217 | // CompactIPv6Addrs is a set of IPv6 Addrs. 218 | type CompactIPv6Addrs []CompactAddr 219 | 220 | // MarshalBinary implements the interface encoding.BinaryMarshaler. 221 | func (cas CompactIPv6Addrs) MarshalBinary() ([]byte, error) { 222 | buf := bytes.NewBuffer(nil) 223 | buf.Grow(18 * len(cas)) 224 | for _, addr := range cas { 225 | addr.IP = addr.IP.To16() 226 | if n, err := addr.WriteBinary(buf); err != nil { 227 | return nil, err 228 | } else if n != 18 { 229 | panic(fmt.Errorf("CompactIPv4Nodes: the invalid node info length '%d'", n)) 230 | } 231 | } 232 | return buf.Bytes(), nil 233 | } 234 | 235 | // UnmarshalBinary implements the interface encoding.BinaryUnmarshaler. 236 | func (cas *CompactIPv6Addrs) UnmarshalBinary(b []byte) (err error) { 237 | _len := len(b) 238 | if _len%18 != 0 { 239 | return fmt.Errorf("CompactIPv4Addrs: invalid addr info length '%d'", _len) 240 | } 241 | 242 | addrs := make(CompactIPv6Addrs, 0, _len/18) 243 | for i := 0; i < _len; i += 18 { 244 | var addr CompactAddr 245 | if err = addr.UnmarshalBinary(b[i : i+18]); err != nil { 246 | return 247 | } 248 | addrs = append(addrs, addr) 249 | } 250 | 251 | *cas = addrs 252 | return 253 | } 254 | 255 | // MarshalBencode implements the interface bencode.Marshaler. 256 | func (cas CompactIPv6Addrs) MarshalBencode() (b []byte, err error) { 257 | if b, err = cas.MarshalBinary(); err == nil { 258 | b, err = bencode.EncodeBytes(b) 259 | } 260 | return 261 | } 262 | 263 | // UnmarshalBencode implements the interface bencode.Unmarshaler. 264 | func (cas *CompactIPv6Addrs) UnmarshalBencode(b []byte) (err error) { 265 | var data []byte 266 | if err = bencode.DecodeBytes(b, &data); err == nil { 267 | err = cas.UnmarshalBinary(data) 268 | } 269 | return 270 | } 271 | -------------------------------------------------------------------------------- /metainfo/addr_compact_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package metainfo 16 | 17 | import ( 18 | "bytes" 19 | "net" 20 | "testing" 21 | 22 | "github.com/xgfone/go-bt/bencode" 23 | ) 24 | 25 | func TestCompactAddr(t *testing.T) { 26 | addrs := []CompactAddr{ 27 | {IP: net.ParseIP("172.16.1.1").To4(), Port: 123}, 28 | {IP: net.ParseIP("192.168.1.1").To4(), Port: 456}, 29 | } 30 | expect := "l6:\xac\x10\x01\x01\x00\x7b6:\xc0\xa8\x01\x01\x01\xc8e" 31 | 32 | buf := new(bytes.Buffer) 33 | if err := bencode.NewEncoder(buf).Encode(addrs); err != nil { 34 | t.Error(err) 35 | } else if result := buf.String(); result != expect { 36 | t.Errorf("expect %s, but got %x\n", expect, result) 37 | } 38 | 39 | var raddrs []CompactAddr 40 | if err := bencode.DecodeString(expect, &raddrs); err != nil { 41 | t.Error(err) 42 | } else if len(raddrs) != len(addrs) { 43 | t.Errorf("expect addrs length %d, but got %d\n", len(addrs), len(raddrs)) 44 | } else { 45 | for i, addr := range addrs { 46 | if !addr.Equal(raddrs[i]) { 47 | t.Errorf("%d: expect addr %v, but got %v\n", i, addr, raddrs[i]) 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /metainfo/addr_host.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package metainfo 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | "net" 21 | "strconv" 22 | 23 | "github.com/xgfone/go-bt/bencode" 24 | ) 25 | 26 | // HostAddr represents an address based on host and port. 27 | type HostAddr struct { 28 | Host string 29 | Port uint16 30 | } 31 | 32 | // NewHostAddr returns a new host Addr. 33 | func NewHostAddr(host string, port uint16) HostAddr { 34 | return HostAddr{Host: host, Port: port} 35 | } 36 | 37 | // ParseHostAddr parses a string s to Addr. 38 | func ParseHostAddr(s string) (HostAddr, error) { 39 | host, port, err := net.SplitHostPort(s) 40 | if err != nil { 41 | return HostAddr{}, err 42 | } 43 | 44 | _port, err := strconv.ParseUint(port, 10, 16) 45 | if err != nil { 46 | return HostAddr{}, err 47 | } 48 | 49 | return NewHostAddr(host, uint16(_port)), nil 50 | } 51 | 52 | func (a HostAddr) String() string { 53 | if a.Port == 0 { 54 | return a.Host 55 | } 56 | return net.JoinHostPort(a.Host, strconv.FormatUint(uint64(a.Port), 10)) 57 | } 58 | 59 | // Equal reports whether a is equal to o. 60 | func (a HostAddr) Equal(o HostAddr) bool { 61 | return a.Port == o.Port && a.Host == o.Host 62 | } 63 | 64 | var ( 65 | _ bencode.Marshaler = new(HostAddr) 66 | _ bencode.Unmarshaler = new(HostAddr) 67 | ) 68 | 69 | // MarshalBencode implements the interface bencode.Marshaler. 70 | func (a HostAddr) MarshalBencode() (b []byte, err error) { 71 | buf := bytes.NewBuffer(nil) 72 | buf.Grow(64) 73 | err = bencode.NewEncoder(buf).Encode([]interface{}{a.Host, a.Port}) 74 | if err == nil { 75 | b = buf.Bytes() 76 | } 77 | return 78 | } 79 | 80 | // UnmarshalBencode implements the interface bencode.Unmarshaler. 81 | func (a *HostAddr) UnmarshalBencode(b []byte) (err error) { 82 | var iface interface{} 83 | if err = bencode.NewDecoder(bytes.NewBuffer(b)).Decode(&iface); err != nil { 84 | return 85 | } 86 | 87 | switch v := iface.(type) { 88 | case string: 89 | *a, err = ParseHostAddr(v) 90 | 91 | case []interface{}: 92 | err = a.decode(v) 93 | 94 | default: 95 | err = fmt.Errorf("unsupported type: %T", iface) 96 | } 97 | 98 | return 99 | } 100 | 101 | func (a *HostAddr) decode(vs []interface{}) (err error) { 102 | defer func() { 103 | switch e := recover().(type) { 104 | case nil: 105 | case error: 106 | err = e 107 | default: 108 | err = fmt.Errorf("%v", e) 109 | } 110 | }() 111 | 112 | a.Host = vs[0].(string) 113 | a.Port = uint16(vs[1].(int64)) 114 | return 115 | } 116 | -------------------------------------------------------------------------------- /metainfo/addr_host_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package metainfo 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/xgfone/go-bt/bencode" 21 | ) 22 | 23 | func TestAddress(t *testing.T) { 24 | addrs := []HostAddr{ 25 | {Host: "1.2.3.4", Port: 123}, 26 | {Host: "www.example.com", Port: 456}, 27 | } 28 | expect := `ll7:1.2.3.4i123eel15:www.example.comi456eee` 29 | 30 | if result, err := bencode.EncodeString(addrs); err != nil { 31 | t.Error(err) 32 | } else if result != expect { 33 | t.Errorf("expect %s, but got %s\n", expect, result) 34 | } 35 | 36 | var raddrs []HostAddr 37 | if err := bencode.DecodeString(expect, &raddrs); err != nil { 38 | t.Error(err) 39 | } else if len(raddrs) != len(addrs) { 40 | t.Errorf("expect addrs length %d, but got %d\n", len(addrs), len(raddrs)) 41 | } else { 42 | for i, addr := range addrs { 43 | if !addr.Equal(raddrs[i]) { 44 | t.Errorf("%d: expect %v, but got %v\n", i, addr, raddrs[i]) 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /metainfo/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package metainfo is used to encode or decode the metainfo of the torrent file 16 | // and the MagNet link. 17 | package metainfo 18 | -------------------------------------------------------------------------------- /metainfo/file.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package metainfo 16 | 17 | import "path/filepath" 18 | 19 | // File represents a file in the multi-file case. 20 | type File struct { 21 | // Length is the length of the file in bytes. 22 | Length int64 `json:"length" bencode:"length"` // BEP 3 23 | 24 | // Paths is a list containing one or more string elements that together 25 | // represent the path and filename. Each element in the list corresponds 26 | // to either a directory name or (in the case of the final element) the 27 | // filename. 28 | // 29 | // For example, a the file "dir1/dir2/file.ext" would consist of three 30 | // string elements: "dir1", "dir2", and "file.ext". This is encoded as 31 | // a bencoded list of strings such as l4:dir14:dir28:file.exte. 32 | Paths []string `json:"path" bencode:"path"` // BEP 3 33 | } 34 | 35 | func (f File) String() string { 36 | return filepath.Join(f.Paths...) 37 | } 38 | 39 | // Path returns the path of the current. 40 | func (f File) Path(info Info) string { 41 | if info.IsDir() { 42 | paths := make([]string, len(f.Paths)+1) 43 | paths[0] = info.Name 44 | copy(paths[1:], f.Paths) 45 | return filepath.Join(paths...) 46 | } 47 | return info.Name 48 | } 49 | 50 | // PathWithPrefix returns the path of the current with the prefix directory. 51 | func (f File) PathWithPrefix(prefix string, info Info) string { 52 | if info.IsDir() { 53 | paths := make([]string, len(f.Paths)+2) 54 | paths[0] = prefix 55 | paths[1] = info.Name 56 | copy(paths[2:], f.Paths) 57 | return filepath.Join(paths...) 58 | } 59 | return filepath.Join(prefix, info.Name) 60 | } 61 | 62 | // Offset returns the offset of the current file from the start. 63 | func (f File) Offset(info Info) (ret int64) { 64 | path := f.Path(info) 65 | for _, file := range info.AllFiles() { 66 | if path == file.Path(info) { 67 | return 68 | } 69 | ret += file.Length 70 | } 71 | panic("not found") 72 | } 73 | 74 | type files []File 75 | 76 | func (fs files) Len() int { return len(fs) } 77 | func (fs files) Less(i, j int) bool { return fs[i].String() < fs[j].String() } 78 | func (fs files) Swap(i, j int) { f := fs[i]; fs[i] = fs[j]; fs[j] = f } 79 | 80 | // FilePieces returns the information of the pieces referred by the file. 81 | func (f File) FilePieces(info Info) (fps FilePieces) { 82 | if f.Length < 1 { 83 | return nil 84 | } 85 | 86 | startOffset := f.Offset(info) 87 | startPieceIndex := startOffset / info.PieceLength 88 | startPieceOffset := startOffset % info.PieceLength 89 | 90 | endOffset := startOffset + f.Length 91 | endPieceIndex := endOffset / info.PieceLength 92 | endPieceOffset := endOffset % info.PieceLength 93 | 94 | if startPieceIndex == endPieceIndex { 95 | return FilePieces{{ 96 | Index: uint32(startPieceIndex), 97 | Offset: uint32(startPieceOffset), 98 | Length: uint32(endPieceOffset - startPieceOffset), 99 | }} 100 | } 101 | 102 | fps = make(FilePieces, 0, endPieceIndex-startPieceIndex+1) 103 | fps = append(fps, FilePiece{ 104 | Index: uint32(startPieceIndex), 105 | Offset: uint32(startPieceOffset), 106 | Length: uint32(info.PieceLength - startPieceOffset), 107 | }) 108 | for i := startPieceIndex + 1; i < endPieceIndex; i++ { 109 | fps = append(fps, FilePiece{Index: uint32(i), Length: uint32(info.PieceLength)}) 110 | } 111 | fps = append(fps, FilePiece{Index: uint32(endPieceIndex), Length: uint32(endPieceOffset)}) 112 | return 113 | } 114 | -------------------------------------------------------------------------------- /metainfo/info.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package metainfo 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | "os" 21 | "path/filepath" 22 | "sort" 23 | "strings" 24 | ) 25 | 26 | // Info is the file inforatino. 27 | type Info struct { 28 | // Name is the name of the file in the single file case. 29 | // Or, it is the name of the directory in the muliple file case. 30 | Name string `json:"name" bencode:"name"` // BEP 3 31 | 32 | // PieceLength is the number of bytes in each piece, which is usually 33 | // a power of 2. 34 | PieceLength int64 `json:"piece length" bencode:"piece length"` // BEP 3 35 | 36 | // Pieces is the concatenation of all 20-byte SHA1 hash values, 37 | // one per piece (byte string, i.e. not urlencoded). 38 | Pieces Hashes `json:"pieces" bencode:"pieces"` // BEP 3 39 | 40 | // Length is the length of the file in bytes in the single file case. 41 | // 42 | // It's mutually exclusive with Files. 43 | Length int64 `json:"length,omitempty" bencode:"length,omitempty"` // BEP 3 44 | 45 | // Files is the list of all the files in the multi-file case. 46 | // 47 | // For the purposes of the other keys, the multi-file case is treated 48 | // as only having a single file by concatenating the files in the order 49 | // they appear in the files list. 50 | // 51 | // It's mutually exclusive with Length. 52 | Files []File `json:"files,omitempty" bencode:"files,omitempty"` // BEP 3 53 | } 54 | 55 | // getAllInfoFiles returns the list of the all files in a certain directory 56 | // recursively. 57 | func getAllInfoFiles(rootDir string) (files []File, err error) { 58 | err = filepath.Walk(rootDir, func(path string, fi os.FileInfo, err error) error { 59 | if err != nil || fi.IsDir() { 60 | return err 61 | } 62 | 63 | relPath, err := filepath.Rel(rootDir, path) 64 | if err != nil { 65 | return fmt.Errorf("error getting relative path: %s", err) 66 | } 67 | 68 | paths := strings.Split(relPath, string(filepath.Separator)) 69 | for _, name := range paths { 70 | if name == ".git" { 71 | return nil 72 | } 73 | } 74 | 75 | files = append(files, File{ 76 | Paths: paths, 77 | Length: fi.Size(), 78 | }) 79 | 80 | return nil 81 | }) 82 | 83 | if err == nil && len(files) == 0 { 84 | err = fmt.Errorf("no files in the directory '%s'", rootDir) 85 | } 86 | 87 | return 88 | } 89 | 90 | // NewInfoFromFilePath returns a new Info from a file or directory. 91 | func NewInfoFromFilePath(root string, pieceLength int64) (info Info, err error) { 92 | root = filepath.Clean(root) 93 | 94 | fi, err := os.Stat(root) 95 | if err != nil { 96 | return 97 | } else if !fi.IsDir() { 98 | info.Length = fi.Size() 99 | } else if info.Files, err = getAllInfoFiles(root); err != nil { 100 | return 101 | } 102 | 103 | sort.Sort(files(info.Files)) 104 | info.Pieces, err = GeneratePiecesFromFiles(info.AllFiles(), pieceLength, 105 | func(file File) (io.ReadCloser, error) { 106 | if _len := len(file.Paths); _len > 0 { 107 | paths := make([]string, 0, _len+1) 108 | paths = append(paths, root) 109 | paths = append(paths, file.Paths...) 110 | return os.Open(filepath.Join(paths...)) 111 | } 112 | return os.Open(root) 113 | }) 114 | 115 | if err == nil { 116 | info.Name = filepath.Base(root) 117 | info.PieceLength = pieceLength 118 | } else { 119 | err = fmt.Errorf("error generating pieces: %s", err) 120 | } 121 | 122 | return 123 | } 124 | 125 | // IsDir reports whether the name is a directory, that's, the file is not 126 | // a single file. 127 | func (info Info) IsDir() bool { return len(info.Files) != 0 } 128 | 129 | // CountPieces returns the number of the pieces. 130 | func (info Info) CountPieces() int { return len(info.Pieces) } 131 | 132 | // TotalLength returns the total length of the torrent file. 133 | func (info Info) TotalLength() (ret int64) { 134 | if info.IsDir() { 135 | for _, fi := range info.Files { 136 | ret += fi.Length 137 | } 138 | } else { 139 | ret = info.Length 140 | } 141 | return 142 | } 143 | 144 | // PieceOffset returns the total offset of the piece. 145 | // 146 | // offset is the offset relative to the beginning of the piece. 147 | func (info Info) PieceOffset(index, offset uint32) int64 { 148 | return int64(index)*info.PieceLength + int64(offset) 149 | } 150 | 151 | // GetFileByOffset returns the file and its offset by the total offset. 152 | // 153 | // If fileOffset is eqaul to file.Length, it means to reach the end. 154 | func (info Info) GetFileByOffset(offset int64) (file File, fileOffset int64) { 155 | if !info.IsDir() { 156 | if offset > info.Length { 157 | panic(fmt.Errorf("offset '%d' exceeds the maximum length '%d'", 158 | offset, info.Length)) 159 | } 160 | return File{Length: info.Length, Paths: []string{info.Name}}, offset 161 | } 162 | 163 | fileOffset = offset 164 | for i, _len := 0, len(info.Files)-1; i <= _len; i++ { 165 | file = info.Files[i] 166 | if fileOffset < file.Length { 167 | return 168 | } else if fileOffset == file.Length && i == _len { 169 | return 170 | } 171 | fileOffset -= file.Length 172 | } 173 | 174 | if fileOffset > file.Length { 175 | panic(fmt.Errorf("offset '%d' exceeds the maximum length '%d'", 176 | offset, info.TotalLength())) 177 | } 178 | 179 | return 180 | } 181 | 182 | // AllFiles returns all the files. 183 | // 184 | // Notice: for the single file, the Path is nil. 185 | func (info Info) AllFiles() []File { 186 | if info.IsDir() { 187 | return info.Files 188 | } 189 | return []File{{Length: info.Length}} 190 | } 191 | -------------------------------------------------------------------------------- /metainfo/info_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package metainfo 16 | 17 | import "testing" 18 | 19 | func TestInfo_GetFileByOffset(t *testing.T) { 20 | FileInfo := Info{ 21 | Name: "test_rw", 22 | PieceLength: 64, 23 | Length: 600, 24 | } 25 | file, fileOffset := FileInfo.GetFileByOffset(0) 26 | if file.Offset(FileInfo) != 0 || fileOffset != 0 { 27 | t.Errorf("expect fileOffset='%d', but got '%d'", 0, fileOffset) 28 | } 29 | file, fileOffset = FileInfo.GetFileByOffset(100) 30 | if file.Offset(FileInfo) != 0 || fileOffset != 100 { 31 | t.Errorf("expect fileOffset='%d', but got '%d'", 100, fileOffset) 32 | } 33 | file, fileOffset = FileInfo.GetFileByOffset(600) 34 | if file.Offset(FileInfo) != 0 || fileOffset != 600 { 35 | t.Errorf("expect fileOffset='%d', but got '%d'", 600, fileOffset) 36 | } 37 | 38 | DirInfo := Info{ 39 | Name: "test_rw", 40 | PieceLength: 64, 41 | Files: []File{ 42 | {Length: 100, Paths: []string{"file1"}}, 43 | {Length: 200, Paths: []string{"file2"}}, 44 | {Length: 300, Paths: []string{"file3"}}, 45 | }, 46 | } 47 | file, fileOffset = DirInfo.GetFileByOffset(0) 48 | if file.Offset(DirInfo) != 0 || file.Length != 100 || fileOffset != 0 { 49 | t.Errorf("expect fileOffset='%d', but got '%d'", 0, fileOffset) 50 | } 51 | file, fileOffset = DirInfo.GetFileByOffset(50) 52 | if file.Offset(DirInfo) != 0 || file.Length != 100 || fileOffset != 50 { 53 | t.Errorf("expect fileOffset='%d', but got '%d'", 50, fileOffset) 54 | } 55 | file, fileOffset = DirInfo.GetFileByOffset(100) 56 | if file.Offset(DirInfo) != 100 || file.Length != 200 || fileOffset != 0 { 57 | t.Errorf("expect fileOffset='%d', but got '%d'", 0, fileOffset) 58 | } 59 | file, fileOffset = DirInfo.GetFileByOffset(200) 60 | if file.Offset(DirInfo) != 100 || file.Length != 200 || fileOffset != 100 { 61 | t.Errorf("expect fileOffset='%d', but got '%d'", 100, fileOffset) 62 | } 63 | file, fileOffset = DirInfo.GetFileByOffset(300) 64 | if file.Offset(DirInfo) != 300 || file.Length != 300 || fileOffset != 0 { 65 | t.Errorf("expect fileOffset='%d', but got '%d'", 0, fileOffset) 66 | } 67 | file, fileOffset = DirInfo.GetFileByOffset(400) 68 | if file.Offset(DirInfo) != 300 || file.Length != 300 || fileOffset != 100 { 69 | t.Errorf("expect fileOffset='%d', but got '%d'", 100, fileOffset) 70 | } 71 | file, fileOffset = DirInfo.GetFileByOffset(600) 72 | if file.Offset(DirInfo) != 300 || file.Length != 300 || fileOffset != 300 { 73 | t.Errorf("expect fileOffset='%d', but got '%d'", 300, fileOffset) 74 | } 75 | } 76 | 77 | func TestNewInfoFromFilePath(t *testing.T) { 78 | info, err := NewInfoFromFilePath("info.go", PieceSize256KB) 79 | if err != nil { 80 | t.Error(err) 81 | } else if info.Name != "info.go" || info.Files != nil { 82 | t.Errorf("invalid info %+v\n", info) 83 | } 84 | 85 | info, err = NewInfoFromFilePath("../metainfo", PieceSize256KB) 86 | if err != nil { 87 | t.Error(err) 88 | } else if info.Name != "metainfo" || info.Files == nil || info.Length > 0 { 89 | t.Errorf("invalid info %+v\n", info) 90 | } 91 | 92 | info, err = NewInfoFromFilePath("../../go-bt", PieceSize256KB) 93 | if err != nil { 94 | t.Error(err) 95 | } else if info.Name != "go-bt" || info.Files == nil || info.Length > 0 { 96 | t.Errorf("invalid info %+v\n", info) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /metainfo/infohash.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package metainfo 16 | 17 | import ( 18 | "bytes" 19 | "crypto/rand" 20 | "crypto/sha1" 21 | "encoding/base32" 22 | "encoding/hex" 23 | "errors" 24 | "fmt" 25 | "io" 26 | 27 | "github.com/xgfone/go-bt/bencode" 28 | ) 29 | 30 | var zeroHash Hash 31 | 32 | // HashSize is the size of the InfoHash. 33 | const HashSize = 20 34 | 35 | // Hash is the 20-byte SHA1 hash used for info and pieces. 36 | type Hash [HashSize]byte 37 | 38 | // NewRandomHash returns a random hash. 39 | func NewRandomHash() (h Hash) { 40 | rand.Read(h[:]) 41 | return 42 | } 43 | 44 | // NewHash converts the 20-bytes to Hash. 45 | func NewHash(b []byte) (h Hash) { 46 | copy(h[:], b[:HashSize]) 47 | return 48 | } 49 | 50 | // NewHashFromString returns a new Hash from a string. 51 | func NewHashFromString(s string) (h Hash) { 52 | err := h.FromString(s) 53 | if err != nil { 54 | panic(err) 55 | } 56 | return 57 | } 58 | 59 | // NewHashFromHexString returns a new Hash from a hex string. 60 | func NewHashFromHexString(s string) (h Hash) { 61 | err := h.FromHexString(s) 62 | if err != nil { 63 | panic(err) 64 | } 65 | return 66 | } 67 | 68 | // NewHashFromBytes returns a new Hash from a byte slice. 69 | func NewHashFromBytes(b []byte) (ret Hash) { 70 | hasher := sha1.New() 71 | hasher.Write(b) 72 | copy(ret[:], hasher.Sum(nil)) 73 | return 74 | } 75 | 76 | // Bytes returns the byte slice type. 77 | func (h Hash) Bytes() []byte { 78 | return h[:] 79 | } 80 | 81 | // String is equal to HexString. 82 | func (h Hash) String() string { 83 | return h.HexString() 84 | } 85 | 86 | // BytesString returns the bytes string, that's, string(h[:]). 87 | func (h Hash) BytesString() string { 88 | return string(h[:]) 89 | } 90 | 91 | // HexString returns the hex string format. 92 | func (h Hash) HexString() string { 93 | return hex.EncodeToString(h[:]) 94 | } 95 | 96 | // IsZero reports whether the whole hash is zero. 97 | func (h Hash) IsZero() bool { 98 | return h == zeroHash 99 | } 100 | 101 | // WriteBinary is the same as MarshalBinary, but writes the result into w 102 | // instead of returning. 103 | func (h Hash) WriteBinary(w io.Writer) (m int, err error) { 104 | return w.Write(h[:]) 105 | } 106 | 107 | // UnmarshalBinary implements the interface binary.BinaryUnmarshaler. 108 | func (h *Hash) UnmarshalBinary(b []byte) (err error) { 109 | if len(b) < HashSize { 110 | return errors.New("Hash.UnmarshalBinary: too few bytes") 111 | } 112 | copy((*h)[:], b[:HashSize]) 113 | return 114 | } 115 | 116 | // MarshalBinary implements the interface binary.BinaryMarshaler. 117 | func (h Hash) MarshalBinary() (data []byte, err error) { 118 | return h[:], nil 119 | } 120 | 121 | // MarshalBencode implements the interface bencode.Marshaler. 122 | func (h Hash) MarshalBencode() (b []byte, err error) { 123 | return bencode.EncodeBytes(h[:]) 124 | } 125 | 126 | // UnmarshalBencode implements the interface bencode.Unmarshaler. 127 | func (h *Hash) UnmarshalBencode(b []byte) (err error) { 128 | var s string 129 | if err = bencode.NewDecoder(bytes.NewBuffer(b)).Decode(&s); err == nil { 130 | err = h.FromString(s) 131 | } 132 | return 133 | } 134 | 135 | // FromString resets the info hash from the string. 136 | func (h *Hash) FromString(s string) (err error) { 137 | switch len(s) { 138 | case HashSize: 139 | copy(h[:], s) 140 | case 2 * HashSize: 141 | err = h.FromHexString(s) 142 | case 32: 143 | var bs []byte 144 | if bs, err = base32.StdEncoding.DecodeString(s); err == nil { 145 | copy(h[:], bs) 146 | } 147 | default: 148 | hasher := sha1.New() 149 | hasher.Write([]byte(s)) 150 | copy(h[:], hasher.Sum(nil)) 151 | } 152 | 153 | return 154 | } 155 | 156 | // FromHexString resets the info hash from the hex string. 157 | func (h *Hash) FromHexString(s string) (err error) { 158 | if len(s) != 2*HashSize { 159 | err = fmt.Errorf("hash hex string has bad length: %d", len(s)) 160 | return 161 | } 162 | 163 | n, err := hex.Decode(h[:], []byte(s)) 164 | if err != nil { 165 | return 166 | } 167 | 168 | if n != HashSize { 169 | panic(n) 170 | } 171 | return 172 | } 173 | 174 | // Xor returns the hash of h XOR o. 175 | func (h Hash) Xor(o Hash) (ret Hash) { 176 | for i := range o { 177 | ret[i] = h[i] ^ o[i] 178 | } 179 | return 180 | } 181 | 182 | // Compare returns 0 if h == o, -1 if h < o, or +1 if h > o. 183 | func (h Hash) Compare(o Hash) int { return bytes.Compare(h[:], o[:]) } 184 | 185 | /// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 186 | 187 | // Hashes is a set of Hashes. 188 | type Hashes []Hash 189 | 190 | // Contains reports whether hs contains h. 191 | func (hs Hashes) Contains(h Hash) bool { 192 | for _, _h := range hs { 193 | if h == _h { 194 | return true 195 | } 196 | } 197 | return false 198 | } 199 | 200 | // MarshalBencode implements the interface bencode.Marshaler. 201 | func (hs Hashes) MarshalBencode() ([]byte, error) { 202 | buf := bytes.NewBuffer(nil) 203 | buf.Grow(HashSize * len(hs)) 204 | for _, h := range hs { 205 | buf.Write(h[:]) 206 | } 207 | return bencode.EncodeBytes(buf.Bytes()) 208 | } 209 | 210 | // UnmarshalBencode implements the interface bencode.Unmarshaler. 211 | func (hs *Hashes) UnmarshalBencode(b []byte) (err error) { 212 | var bs []byte 213 | if err = bencode.DecodeBytes(b, &bs); err != nil { 214 | return 215 | } 216 | 217 | _len := len(bs) 218 | if _len%HashSize != 0 { 219 | return fmt.Errorf("Hashes: invalid bytes length '%d'", _len) 220 | } 221 | 222 | hashes := make(Hashes, 0, _len/HashSize) 223 | for i := 0; i < _len; i += HashSize { 224 | var h Hash 225 | copy(h[:], bs[i:i+HashSize]) 226 | hashes = append(hashes, h) 227 | } 228 | 229 | *hs = hashes 230 | return 231 | } 232 | -------------------------------------------------------------------------------- /metainfo/infohash_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package metainfo 16 | 17 | import "testing" 18 | 19 | func TestHash(t *testing.T) { 20 | hexHash := "0001020304050607080909080706050403020100" 21 | 22 | b, err := NewHashFromHexString(hexHash).MarshalBencode() 23 | if err != nil { 24 | t.Fatal(err) 25 | } 26 | 27 | var h Hash 28 | if err = h.UnmarshalBencode(b); err != nil { 29 | t.Fatal(err) 30 | } 31 | 32 | if hexs := h.String(); hexs != hexHash { 33 | t.Errorf("expect '%s', but got '%s'\n", hexHash, hexs) 34 | } 35 | 36 | h = Hash{} 37 | hexHash = "0001020304050607080900010203040506070809" 38 | err = h.UnmarshalBinary([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}) 39 | if err != nil { 40 | t.Error(err) 41 | } else if hexs := h.HexString(); hexs != hexHash { 42 | t.Errorf("expect '%s', but got '%s'\n", hexHash, hexs) 43 | } 44 | } 45 | 46 | func TestHashes(t *testing.T) { 47 | hexHash1 := "0101010101010101010101010101010101010101" 48 | hexHash2 := "0202020202020202020202020202020202020202" 49 | 50 | hashes := Hashes{ 51 | NewHashFromHexString(hexHash1), 52 | NewHashFromHexString(hexHash2), 53 | } 54 | 55 | b, err := hashes.MarshalBencode() 56 | if err != nil { 57 | t.Fatal(err) 58 | } 59 | 60 | hashes = Hashes{} 61 | if err = hashes.UnmarshalBencode(b); err != nil { 62 | t.Fatal(err) 63 | } 64 | 65 | if _len := len(hashes); _len != 2 { 66 | t.Fatalf("expect the len(hashes)==2, but got '%d'", _len) 67 | } 68 | 69 | for i, h := range hashes { 70 | if i == 0 { 71 | if hexs := h.HexString(); hexs != hexHash1 { 72 | t.Errorf("index %d: expect '%s', but got '%s'\n", i, hexHash1, hexs) 73 | } 74 | } else { 75 | if hexs := h.HexString(); hexs != hexHash2 { 76 | t.Errorf("index %d: expect '%s', but got '%s'\n", i, hexHash2, hexs) 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /metainfo/magnet.go: -------------------------------------------------------------------------------- 1 | // Mozilla Public License Version 2.0 2 | // Modify from github.com/anacrolix/torrent/metainfo. 3 | 4 | package metainfo 5 | 6 | import ( 7 | "encoding/base32" 8 | "encoding/hex" 9 | "errors" 10 | "fmt" 11 | "net/url" 12 | "strings" 13 | ) 14 | 15 | // Magnet link components. 16 | type Magnet struct { 17 | InfoHash Hash // From "xt" 18 | Trackers []string // From "tr" 19 | DisplayName string // From "dn" if not empty 20 | Params url.Values // All other values, such as "as", "xs", etc 21 | } 22 | 23 | const xtPrefix = "urn:btih:" 24 | 25 | // Peers returns the list of the addresses of the peers. 26 | // 27 | // See BEP 9 28 | func (m Magnet) Peers() (peers []HostAddr, err error) { 29 | vs := m.Params["x.pe"] 30 | peers = make([]HostAddr, 0, len(vs)) 31 | for _, v := range vs { 32 | if v != "" { 33 | addr, err := ParseHostAddr(v) 34 | if err != nil { 35 | return nil, err 36 | } 37 | peers = append(peers, addr) 38 | } 39 | } 40 | return 41 | } 42 | 43 | func (m Magnet) String() string { 44 | vs := make(url.Values, len(m.Params)+len(m.Trackers)+2) 45 | for k, v := range m.Params { 46 | vs[k] = append([]string(nil), v...) 47 | } 48 | 49 | for _, tr := range m.Trackers { 50 | vs.Add("tr", tr) 51 | } 52 | if m.DisplayName != "" { 53 | vs.Add("dn", m.DisplayName) 54 | } 55 | 56 | // Transmission and Deluge both expect "urn:btih:" to be unescaped. 57 | // Deluge wants it to be at the start of the magnet link. 58 | // The InfoHash field is expected to be BitTorrent in this implementation. 59 | u := url.URL{ 60 | Scheme: "magnet", 61 | RawQuery: "xt=" + xtPrefix + m.InfoHash.HexString(), 62 | } 63 | if len(vs) != 0 { 64 | u.RawQuery += "&" + vs.Encode() 65 | } 66 | return u.String() 67 | } 68 | 69 | // ParseMagnetURI parses Magnet-formatted URIs into a Magnet instance. 70 | func ParseMagnetURI(uri string) (m Magnet, err error) { 71 | u, err := url.Parse(uri) 72 | if err != nil { 73 | err = fmt.Errorf("error parsing uri: %s", err) 74 | return 75 | } else if u.Scheme != "magnet" { 76 | err = fmt.Errorf("unexpected scheme %q", u.Scheme) 77 | return 78 | } 79 | 80 | q := u.Query() 81 | xt := q.Get("xt") 82 | if m.InfoHash, err = parseInfohash(q.Get("xt")); err != nil { 83 | err = fmt.Errorf("error parsing infohash %q: %s", xt, err) 84 | return 85 | } 86 | dropFirst(q, "xt") 87 | 88 | m.DisplayName = q.Get("dn") 89 | dropFirst(q, "dn") 90 | 91 | m.Trackers = q["tr"] 92 | delete(q, "tr") 93 | 94 | if len(q) == 0 { 95 | q = nil 96 | } 97 | 98 | m.Params = q 99 | return 100 | } 101 | 102 | func parseInfohash(xt string) (ih Hash, err error) { 103 | if !strings.HasPrefix(xt, xtPrefix) { 104 | err = errors.New("bad xt parameter prefix") 105 | return 106 | } 107 | 108 | var n int 109 | encoded := xt[len(xtPrefix):] 110 | switch len(encoded) { 111 | case 40: 112 | n, err = hex.Decode(ih[:], []byte(encoded)) 113 | case 32: 114 | n, err = base32.StdEncoding.Decode(ih[:], []byte(encoded)) 115 | default: 116 | err = fmt.Errorf("unhandled xt parameter encoding (encoded length %d)", len(encoded)) 117 | return 118 | } 119 | 120 | if err != nil { 121 | err = fmt.Errorf("error decoding xt: %s", err) 122 | } else if n != 20 { 123 | panic(fmt.Errorf("invalid length '%d' of the decoded bytes", n)) 124 | } 125 | 126 | return 127 | } 128 | 129 | func dropFirst(vs url.Values, key string) { 130 | sl := vs[key] 131 | switch len(sl) { 132 | case 0, 1: 133 | vs.Del(key) 134 | default: 135 | vs[key] = sl[1:] 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /metainfo/metainfo.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package metainfo 16 | 17 | import ( 18 | "errors" 19 | "io" 20 | "os" 21 | "strings" 22 | 23 | "github.com/xgfone/go-bt/bencode" 24 | "github.com/xgfone/go-bt/internal/helper" 25 | ) 26 | 27 | // Bytes is the []byte type. 28 | type Bytes = bencode.RawMessage 29 | 30 | // AnnounceList is a list of the announces. 31 | type AnnounceList [][]string 32 | 33 | // Unique returns the list of the unique announces. 34 | func (al AnnounceList) Unique() (announces []string) { 35 | announces = make([]string, 0, len(al)) 36 | for _, tier := range al { 37 | for _, v := range tier { 38 | if v != "" && !helper.ContainsString(announces, v) { 39 | announces = append(announces, v) 40 | } 41 | } 42 | } 43 | return 44 | } 45 | 46 | // URLList represents a list of the url. 47 | // 48 | // BEP 19 49 | type URLList []string 50 | 51 | // FullURL returns the index-th full url. 52 | // 53 | // For the single-file case, name is the "name" of "info". 54 | // For the multi-file case, name is the path "name/path/file" 55 | // from "info" and "files". 56 | // 57 | // See http://bittorrent.org/beps/bep_0019.html 58 | func (us URLList) FullURL(index int, name string) (url string) { 59 | if url = us[index]; strings.HasSuffix(url, "/") { 60 | url += name 61 | } 62 | return 63 | } 64 | 65 | // MarshalBencode implements the interface bencode.Marshaler. 66 | func (us URLList) MarshalBencode() (b []byte, err error) { 67 | return bencode.EncodeBytes([]string(us)) 68 | } 69 | 70 | // UnmarshalBencode implements the interface bencode.Unmarshaler. 71 | func (us *URLList) UnmarshalBencode(b []byte) (err error) { 72 | var v interface{} 73 | if err = bencode.DecodeBytes(b, &v); err == nil { 74 | switch vs := v.(type) { 75 | case string: 76 | *us = URLList{vs} 77 | case []interface{}: 78 | urls := make(URLList, len(vs)) 79 | for i, u := range vs { 80 | s, ok := u.(string) 81 | if !ok { 82 | return errors.New("the element of 'url-list' is not string") 83 | } 84 | urls[i] = s 85 | } 86 | *us = urls 87 | default: 88 | return errors.New("invalid 'url-lsit'") 89 | } 90 | } 91 | return 92 | } 93 | 94 | // MetaInfo represents the .torrent file. 95 | type MetaInfo struct { 96 | InfoBytes Bytes `bencode:"info"` // BEP 3 97 | Announce string `bencode:"announce,omitempty"` // BEP 3, Single Tracker 98 | AnnounceList AnnounceList `bencode:"announce-list,omitempty"` // BEP 12, Multi-Tracker 99 | Nodes []HostAddr `bencode:"nodes,omitempty"` // BEP 5, DHT 100 | URLList URLList `bencode:"url-list,omitempty"` // BEP 19, WebSeed 101 | 102 | // Where's this specified? 103 | // Mentioned at https://wiki.theory.org/index.php/BitTorrentSpecification. 104 | // All of them are optional. 105 | 106 | // CreationDate is the creation time of the torrent, in standard UNIX epoch 107 | // format (seconds since 1-Jan-1970 00:00:00 UTC). 108 | CreationDate int64 `bencode:"creation date,omitempty"` 109 | // Comment is the free-form textual comments of the author. 110 | Comment string `bencode:"comment,omitempty"` 111 | // CreatedBy is name and version of the program used to create the .torrent. 112 | CreatedBy string `bencode:"created by,omitempty"` 113 | // Encoding is the string encoding format used to generate the pieces part 114 | // of the info dictionary in the .torrent metafile. 115 | Encoding string `bencode:"encoding,omitempty"` 116 | } 117 | 118 | // Load loads a MetaInfo from an io.Reader. 119 | func Load(r io.Reader) (mi MetaInfo, err error) { 120 | err = bencode.NewDecoder(r).Decode(&mi) 121 | return 122 | } 123 | 124 | // LoadFromFile loads a MetaInfo from a file. 125 | func LoadFromFile(filename string) (mi MetaInfo, err error) { 126 | f, err := os.Open(filename) 127 | if err == nil { 128 | defer f.Close() 129 | mi, err = Load(f) 130 | } 131 | return 132 | } 133 | 134 | // Announces returns all the announces. 135 | func (mi MetaInfo) Announces() AnnounceList { 136 | if len(mi.AnnounceList) > 0 { 137 | return mi.AnnounceList 138 | } else if mi.Announce != "" { 139 | return [][]string{{mi.Announce}} 140 | } 141 | return nil 142 | } 143 | 144 | // Magnet creates a Magnet from a MetaInfo. 145 | // 146 | // If displayName or infoHash is empty, it will be got from the info part. 147 | func (mi MetaInfo) Magnet(displayName string, infoHash Hash) (m Magnet) { 148 | for _, t := range mi.Announces().Unique() { 149 | m.Trackers = append(m.Trackers, t) 150 | } 151 | 152 | if displayName == "" { 153 | info, _ := mi.Info() 154 | displayName = info.Name 155 | } 156 | 157 | if infoHash.IsZero() { 158 | infoHash = mi.InfoHash() 159 | } 160 | 161 | m.DisplayName = displayName 162 | m.InfoHash = infoHash 163 | return 164 | } 165 | 166 | // Write encodes the metainfo to w. 167 | func (mi MetaInfo) Write(w io.Writer) error { 168 | return bencode.NewEncoder(w).Encode(mi) 169 | } 170 | 171 | // InfoHash returns the hash of the info. 172 | func (mi MetaInfo) InfoHash() Hash { 173 | return NewHashFromBytes(mi.InfoBytes) 174 | } 175 | 176 | // Info parses the InfoBytes to the Info. 177 | func (mi MetaInfo) Info() (info Info, err error) { 178 | err = bencode.DecodeBytes(mi.InfoBytes, &info) 179 | return 180 | } 181 | -------------------------------------------------------------------------------- /metainfo/piece.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package metainfo 16 | 17 | import ( 18 | "crypto/sha1" 19 | "errors" 20 | "fmt" 21 | "io" 22 | "sort" 23 | 24 | "github.com/xgfone/go-bt/internal/helper" 25 | ) 26 | 27 | // BlockSize is the default size of a piece block. 28 | const BlockSize = 16 * 1024 // 2^14 = 16KB 29 | 30 | // Predefine some sizes of the pieces. 31 | const ( 32 | PieceSize256KB = 1024 * 256 33 | PieceSize512KB = 2 * PieceSize256KB 34 | PieceSize1MB = 2 * PieceSize512KB 35 | PieceSize2MB = 2 * PieceSize1MB 36 | PieceSize4MB = 2 * PieceSize2MB 37 | ) 38 | 39 | // Piece represents a torrent file piece. 40 | type Piece struct { 41 | info Info 42 | index int 43 | } 44 | 45 | // Piece returns the Piece by the index starting with 0. 46 | func (info Info) Piece(index int) Piece { 47 | if n := len(info.Pieces); index >= n { 48 | panic(fmt.Errorf("Info.Piece: index '%d' exceeds maximum '%d'", index, n)) 49 | } 50 | return Piece{info: info, index: index} 51 | } 52 | 53 | // Index returns the index of the current piece. 54 | func (p Piece) Index() int { return p.index } 55 | 56 | // Offset returns the offset that the current piece is in all the files. 57 | func (p Piece) Offset() int64 { return int64(p.index) * p.info.PieceLength } 58 | 59 | // Hash returns the hash representation of the piece. 60 | func (p Piece) Hash() (h Hash) { return p.info.Pieces[p.index] } 61 | 62 | // Length returns the length of the current piece. 63 | func (p Piece) Length() int64 { 64 | if p.index == p.info.CountPieces()-1 { 65 | return p.info.TotalLength() - int64(p.index)*p.info.PieceLength 66 | } 67 | return p.info.PieceLength 68 | } 69 | 70 | // GeneratePieces generates the pieces from the reader. 71 | func GeneratePieces(r io.Reader, pieceLength int64) (hs Hashes, err error) { 72 | buf := make([]byte, pieceLength) 73 | for { 74 | h := sha1.New() 75 | written, err := helper.CopyNBuffer(h, r, pieceLength, buf) 76 | if written > 0 { 77 | hs = append(hs, NewHash(h.Sum(nil))) 78 | } 79 | 80 | if err == io.EOF { 81 | return hs, nil 82 | } 83 | 84 | if err != nil { 85 | return nil, err 86 | } 87 | } 88 | } 89 | 90 | func writeFiles(w io.Writer, files []File, open func(File) (io.ReadCloser, error)) error { 91 | buf := make([]byte, 8192) 92 | for _, file := range files { 93 | r, err := open(file) 94 | if err != nil { 95 | return fmt.Errorf("error opening %s: %s", file, err) 96 | } 97 | 98 | n, err := helper.CopyNBuffer(w, r, file.Length, buf) 99 | r.Close() 100 | 101 | if n != file.Length { 102 | return fmt.Errorf("error copying %s: %s", file, err) 103 | } 104 | } 105 | return nil 106 | } 107 | 108 | // GeneratePiecesFromFiles generates the pieces from the files. 109 | func GeneratePiecesFromFiles(files []File, pieceLength int64, 110 | open func(File) (io.ReadCloser, error)) (Hashes, error) { 111 | if pieceLength <= 0 { 112 | return nil, errors.New("piece length must be a positive integer") 113 | } 114 | 115 | pr, pw := io.Pipe() 116 | defer pr.Close() 117 | 118 | go func() { pw.CloseWithError(writeFiles(pw, files, open)) }() 119 | return GeneratePieces(pr, pieceLength) 120 | } 121 | 122 | // PieceBlock represents a block in a piece. 123 | type PieceBlock struct { 124 | Index uint32 // The index of the piece. 125 | Offset uint32 // The offset from the beginning of the piece. 126 | Length uint32 // The length of the block, which is equal to 2^14 in general. 127 | } 128 | 129 | // PieceBlocks is a set of PieceBlocks. 130 | type PieceBlocks []PieceBlock 131 | 132 | func (pbs PieceBlocks) Len() int { return len(pbs) } 133 | func (pbs PieceBlocks) Swap(i, j int) { pbs[i], pbs[j] = pbs[j], pbs[i] } 134 | func (pbs PieceBlocks) Less(i, j int) bool { 135 | if pbs[i].Index < pbs[j].Index { 136 | return true 137 | } else if pbs[i].Index == pbs[j].Index && pbs[i].Offset < pbs[j].Offset { 138 | return true 139 | } 140 | return false 141 | } 142 | 143 | // FilePiece represents the piece range used by a file, which is used to 144 | // calculate the downloaded piece when downloading the file. 145 | type FilePiece struct { 146 | // The index of the current piece. 147 | Index uint32 148 | 149 | // The offset bytes from the beginning of the current piece, 150 | // which is equal to 0 in general. 151 | Offset uint32 152 | 153 | // The length of the data, which is equal to PieceLength in Info in general. 154 | // For most implementations, PieceLength is equal to 2^18. 155 | // So, a piece can contain sixteen blocks. 156 | Length uint32 157 | } 158 | 159 | // TotalOffset return the total offset from the beginning of all the files. 160 | func (fp FilePiece) TotalOffset(pieceLength int64) int64 { 161 | return int64(fp.Index)*pieceLength + int64(fp.Offset) 162 | } 163 | 164 | // Blocks returns the lists of the blocks of the piece. 165 | func (fp FilePiece) Blocks() PieceBlocks { 166 | bs := make(PieceBlocks, 0, 16) 167 | for offset, rest := fp.Offset, fp.Length; rest > 0; { 168 | length := uint32(16384) 169 | if rest < length { 170 | length = rest 171 | } 172 | 173 | bs = append(bs, PieceBlock{Index: fp.Index, Offset: offset, Length: length}) 174 | offset += length 175 | rest -= length 176 | } 177 | return bs 178 | } 179 | 180 | // FilePieces is a set of FilePieces. 181 | type FilePieces []FilePiece 182 | 183 | func (fps FilePieces) Len() int { return len(fps) } 184 | func (fps FilePieces) Swap(i, j int) { fps[i], fps[j] = fps[j], fps[i] } 185 | func (fps FilePieces) Less(i, j int) bool { 186 | if fps[i].Index < fps[j].Index { 187 | return true 188 | } else if fps[i].Index == fps[j].Index && fps[i].Offset < fps[j].Offset { 189 | return true 190 | } 191 | return false 192 | } 193 | 194 | // Merge merges the contiguous pieces to the one piece. 195 | func (fps FilePieces) Merge() FilePieces { 196 | _len := len(fps) 197 | if _len < 2 { 198 | return fps 199 | } 200 | sort.Sort(fps) 201 | results := make(FilePieces, 0, _len) 202 | 203 | lastpos := 0 204 | curindex := fps[0].Index 205 | for i, fp := range fps { 206 | if fp.Index != curindex { 207 | results = fps.merge(fps[lastpos:i], results) 208 | lastpos = i 209 | curindex = fp.Index 210 | } 211 | } 212 | 213 | if lastpos < _len { 214 | results = fps.merge(fps[lastpos:_len], results) 215 | } 216 | 217 | return results 218 | } 219 | 220 | func (fps FilePieces) merge(fpset, results FilePieces) FilePieces { 221 | switch _len := len(fpset); _len { 222 | case 0: 223 | case 1: 224 | results = append(results, fpset[0]) 225 | default: 226 | index := fpset[0].Index 227 | offset := fpset[0].Offset 228 | length := fpset[0].Length 229 | 230 | var last int 231 | for i := 1; i < _len; i++ { 232 | fp := fpset[i] 233 | 234 | if offset+length == fp.Offset { 235 | length += fp.Length 236 | continue 237 | } 238 | 239 | last = i 240 | results = append(results, FilePiece{ 241 | Index: index, 242 | Offset: offset, 243 | Length: length, 244 | }) 245 | 246 | offset = fp.Offset 247 | length = fp.Length 248 | } 249 | 250 | if last < _len { 251 | results = append(results, FilePiece{ 252 | Index: index, 253 | Offset: offset, 254 | Length: length, 255 | }) 256 | } 257 | } 258 | 259 | return results 260 | } 261 | -------------------------------------------------------------------------------- /metainfo/piece_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package metainfo 16 | 17 | import "testing" 18 | 19 | func TestFilePieces_Merge(t *testing.T) { 20 | fps := FilePieces{ 21 | { 22 | Index: 1, 23 | Offset: 100, 24 | Length: 100, 25 | }, 26 | { 27 | Index: 1, 28 | Offset: 400, 29 | Length: 100, 30 | }, 31 | { 32 | Index: 2, 33 | Offset: 500, 34 | Length: 100, 35 | }, 36 | { 37 | Index: 2, 38 | Offset: 700, 39 | Length: 100, 40 | }, 41 | { 42 | Index: 1, 43 | Offset: 500, 44 | Length: 100, 45 | }, 46 | { 47 | Index: 2, 48 | Offset: 200, 49 | Length: 100, 50 | }, 51 | { 52 | Index: 2, 53 | Offset: 300, 54 | Length: 100, 55 | }, 56 | { 57 | Index: 1, 58 | Offset: 300, 59 | Length: 100, 60 | }, 61 | } 62 | 63 | fps = fps.Merge() 64 | 65 | if len(fps) != 5 { 66 | t.Error(fps) 67 | } else if fps[0].Index != 1 || fps[0].Offset != 100 || fps[0].Length != 100 { 68 | t.Error(fps[0]) 69 | } else if fps[1].Index != 1 || fps[1].Offset != 300 || fps[1].Length != 300 { 70 | t.Error(fps[1]) 71 | } else if fps[2].Index != 2 || fps[2].Offset != 200 || fps[2].Length != 200 { 72 | t.Error(fps[2]) 73 | } else if fps[3].Index != 2 || fps[3].Offset != 500 || fps[3].Length != 100 { 74 | t.Error(fps[3]) 75 | } else if fps[4].Index != 2 || fps[4].Offset != 700 || fps[4].Length != 100 { 76 | t.Error(fps[4]) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /metainfo/reader.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package metainfo 16 | 17 | import ( 18 | "io" 19 | "os" 20 | ) 21 | 22 | // Reader is used to read the data referred by the torrent file. 23 | type Reader interface { 24 | io.Closer 25 | io.ReaderAt 26 | 27 | Info() Info 28 | ReadBlock(pieceIndex, pieceOffset uint32, p []byte) (int, error) 29 | } 30 | 31 | // reader is used to read the data referred by the torrent file. 32 | type reader struct { 33 | info Info 34 | root string 35 | files map[string]*os.File 36 | } 37 | 38 | // NewReader returns a new Reader. 39 | func NewReader(rootDir string, info Info) Reader { 40 | return &reader{ 41 | root: rootDir, 42 | info: info, 43 | files: make(map[string]*os.File, len(info.Files)), 44 | } 45 | } 46 | 47 | func (r *reader) Info() Info { return r.info } 48 | 49 | // Close implements the interface io.Closer to closes the opened files. 50 | func (r *reader) Close() error { 51 | for name, file := range r.files { 52 | if file != nil { 53 | file.Close() 54 | delete(r.files, name) 55 | } 56 | } 57 | return nil 58 | } 59 | 60 | // ReadBlock reads a data block. 61 | func (r *reader) ReadBlock(pieceIndex, pieceOffset uint32, p []byte) (int, error) { 62 | return r.ReadAt(p, r.info.PieceOffset(pieceIndex, pieceOffset)) 63 | } 64 | 65 | // ReadAt implements the interface io.ReaderAt. 66 | func (r *reader) ReadAt(p []byte, offset int64) (n int, err error) { 67 | var m int 68 | var f *os.File 69 | 70 | for _len := len(p); n < _len; { 71 | file, fileOffset := r.info.GetFileByOffset(offset) 72 | if file.Length == 0 || file.Length == fileOffset { 73 | err = io.EOF 74 | break 75 | } 76 | 77 | pend := n + int(file.Length-fileOffset) 78 | if pend > _len { 79 | pend = _len 80 | } 81 | 82 | filename := file.PathWithPrefix(r.root, r.info) 83 | if f, err = r.open(filename); err != nil { 84 | break 85 | } 86 | 87 | m, err = f.ReadAt(p[n:pend], fileOffset) 88 | n += m 89 | offset += int64(m) 90 | if err != nil { 91 | break 92 | } 93 | } 94 | 95 | return 96 | } 97 | 98 | func (r *reader) open(filename string) (f *os.File, err error) { 99 | if r.files == nil { 100 | r.files = make(map[string]*os.File, len(r.info.Files)) 101 | } 102 | 103 | f, ok := r.files[filename] 104 | if !ok { 105 | if f, err = os.Open(filename); err == nil { 106 | r.files[filename] = f 107 | } 108 | } 109 | return 110 | } 111 | -------------------------------------------------------------------------------- /metainfo/reader_writer_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package metainfo 16 | 17 | import ( 18 | "bytes" 19 | "os" 20 | "testing" 21 | ) 22 | 23 | func generateTestPieceData(len int, numData byte) []byte { 24 | b := make([]byte, len) 25 | for i := 0; i < len; i++ { 26 | b[i] = numData 27 | } 28 | return b 29 | } 30 | 31 | func TestWriterAndReader(t *testing.T) { 32 | info := Info{ 33 | Name: "test_rw", 34 | PieceLength: 64, 35 | Files: []File{ 36 | {Length: 100, Paths: []string{"file1"}}, 37 | {Length: 200, Paths: []string{"file2"}}, 38 | {Length: 300, Paths: []string{"file3"}}, 39 | }, 40 | } 41 | 42 | r := NewReader("", info) 43 | w := NewWriter("", info, 0600) 44 | defer func() { 45 | w.Close() 46 | r.Close() 47 | os.RemoveAll("test_rw") 48 | }() 49 | 50 | datalen := 600 51 | wdata := make([]byte, datalen) 52 | for i := 0; i < datalen; i++ { 53 | wdata[i] = byte(i%26 + 97) 54 | } 55 | 56 | n, err := w.WriteAt(wdata, 0) 57 | if err != nil { 58 | t.Error(err) 59 | return 60 | } else if n != len(wdata) { 61 | t.Errorf("expect wrote '%d', but got '%d'\n", len(wdata), n) 62 | return 63 | } 64 | 65 | rdata := make([]byte, datalen) 66 | n, err = r.ReadAt(rdata, 0) 67 | if err != nil { 68 | t.Error(err) 69 | return 70 | } else if n != len(rdata) { 71 | t.Errorf("expect read '%d', but got '%d'\n", len(rdata), n) 72 | return 73 | } 74 | 75 | if bytes.Compare(rdata, wdata) != 0 { 76 | t.Errorf("expect read '%x', but got '%x'\n", wdata, rdata) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /metainfo/writer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package metainfo 16 | 17 | import ( 18 | "io" 19 | "os" 20 | "path/filepath" 21 | ) 22 | 23 | // Writer is used to write the data referred by the torrent file. 24 | type Writer interface { 25 | io.Closer 26 | io.WriterAt 27 | 28 | Info() Info 29 | WriteBlock(pieceIndex, pieceOffset uint32, p []byte) (int, error) 30 | } 31 | 32 | const wflag = os.O_WRONLY | os.O_CREATE 33 | 34 | type writer struct { 35 | info Info 36 | root string 37 | mode os.FileMode 38 | files map[string]*os.File 39 | } 40 | 41 | // NewWriter returns a new Writer. 42 | // 43 | // If fileMode is equal to 0, it is 0600 by default. 44 | // 45 | // Notice: fileMode is only used when writing the data. 46 | func NewWriter(rootDir string, info Info, fileMode os.FileMode) Writer { 47 | if fileMode == 0 { 48 | fileMode = 0600 49 | } 50 | 51 | return &writer{ 52 | root: rootDir, 53 | info: info, 54 | mode: fileMode, 55 | files: make(map[string]*os.File, len(info.Files)), 56 | } 57 | } 58 | 59 | func (w *writer) open(filename string) (f *os.File, err error) { 60 | if w.files == nil { 61 | w.files = make(map[string]*os.File, len(w.info.Files)) 62 | } 63 | 64 | f, ok := w.files[filename] 65 | if !ok { 66 | if err = os.MkdirAll(filepath.Dir(filename), 0700); err == nil { 67 | if f, err = os.OpenFile(filename, wflag, w.mode); err == nil { 68 | w.files[filename] = f 69 | } 70 | } 71 | } 72 | 73 | return 74 | } 75 | 76 | func (w *writer) Info() Info { return w.info } 77 | 78 | // Close implements the interface io.Closer to closes the opened files. 79 | func (w *writer) Close() error { 80 | for name, file := range w.files { 81 | if file != nil { 82 | file.Close() 83 | delete(w.files, name) 84 | } 85 | } 86 | return nil 87 | } 88 | 89 | // WriteBlock writes a block data. 90 | func (w *writer) WriteBlock(pieceIndex, pieceOffset uint32, p []byte) (int, error) { 91 | return w.WriteAt(p, w.info.PieceOffset(pieceIndex, pieceOffset)) 92 | } 93 | 94 | // WriteAt implements the interface io.WriterAt. 95 | func (w *writer) WriteAt(p []byte, offset int64) (n int, err error) { 96 | var m int 97 | var f *os.File 98 | 99 | for _len := len(p); n < _len; { 100 | file, fileOffset := w.info.GetFileByOffset(offset) 101 | if file.Length == 0 || file.Length == fileOffset { 102 | err = io.ErrShortWrite 103 | break 104 | } 105 | 106 | length := int(file.Length-fileOffset) + n 107 | if _len < length { 108 | length = _len 109 | } else if length <= n { 110 | break 111 | } 112 | 113 | filename := file.PathWithPrefix(w.root, w.info) 114 | if f, err = w.open(filename); err != nil { 115 | break 116 | } 117 | 118 | m, err = f.WriteAt(p[n:length], fileOffset) 119 | n += m 120 | offset += int64(m) 121 | if err != nil { 122 | break 123 | } 124 | 125 | f.Sync() 126 | } 127 | 128 | return 129 | } 130 | -------------------------------------------------------------------------------- /peerprotocol/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package peerprotocol implements the core BT peer protocol. You can use it 16 | // to implement a peer client, and upload/download the file to/from other peers. 17 | // 18 | // See TorrentDownloader to download the torrent file from other peer. 19 | package peerprotocol 20 | -------------------------------------------------------------------------------- /peerprotocol/extension.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package peerprotocol 16 | 17 | import ( 18 | "bytes" 19 | "errors" 20 | "net" 21 | 22 | "github.com/xgfone/go-bt/bencode" 23 | ) 24 | 25 | var errInvalidIP = errors.New("invalid ipv4 or ipv6") 26 | 27 | // Predefine some extended message identifiers. 28 | const ( 29 | ExtendedIDHandshake = 0 // BEP 10 30 | ) 31 | 32 | // Predefine some extended message names. 33 | const ( 34 | ExtendedMessageNameMetadata = "ut_metadata" // BEP 9 35 | ExtendedMessageNamePex = "ut_pex" // BEP 11 36 | ) 37 | 38 | // Predefine some "ut_metadata" extended message types. 39 | const ( 40 | UtMetadataExtendedMsgTypeRequest = 0 // BEP 9 41 | UtMetadataExtendedMsgTypeData = 1 // BEP 9 42 | UtMetadataExtendedMsgTypeReject = 2 // BEP 9 43 | ) 44 | 45 | // CompactIP is used to handle the compact ipv4 or ipv6. 46 | type CompactIP net.IP 47 | 48 | func (ci CompactIP) String() string { 49 | return net.IP(ci).String() 50 | } 51 | 52 | // MarshalBencode implements the interface bencode.Marshaler. 53 | func (ci CompactIP) MarshalBencode() ([]byte, error) { 54 | ip := net.IP(ci) 55 | if ipv4 := ip.To4(); len(ipv4) != 0 { 56 | ip = ipv4 57 | } 58 | return bencode.EncodeBytes(ip[:]) 59 | } 60 | 61 | // UnmarshalBencode implements the interface bencode.Unmarshaler. 62 | func (ci *CompactIP) UnmarshalBencode(b []byte) (err error) { 63 | var ip net.IP 64 | if err = bencode.DecodeBytes(b, &ip); err != nil { 65 | return 66 | } 67 | 68 | switch len(ip) { 69 | case net.IPv4len, net.IPv6len: 70 | default: 71 | return errInvalidIP 72 | } 73 | 74 | if ipv4 := ip.To4(); len(ipv4) != 0 { 75 | ip = ipv4 76 | } 77 | 78 | *ci = CompactIP(ip) 79 | return 80 | } 81 | 82 | // ExtendedHandshakeMsg represent the extended handshake message. 83 | // 84 | // BEP 10 85 | type ExtendedHandshakeMsg struct { 86 | // M is the type of map[ExtendedMessageName]ExtendedMessageID. 87 | M map[string]uint8 `bencode:"m"` // BEP 10 88 | V string `bencode:"v,omitempty"` // BEP 10 89 | Reqq uint64 `bencode:"reqq,omitempty"` // BEP 10. The default in in libtorrent is 250. 90 | 91 | // Port is the local client port, which is redundant and no need 92 | // for the receiving side of the connection to send this. 93 | Port uint16 `bencode:"p,omitempty"` // BEP 10 94 | IPv6 net.IP `bencode:"ipv6,omitempty"` // BEP 10 95 | IPv4 CompactIP `bencode:"ipv4,omitempty"` // BEP 10 96 | YourIP CompactIP `bencode:"yourip,omitempty"` // BEP 10 97 | 98 | MetadataSize uint64 `bencode:"metadata_size,omitempty"` // BEP 9 99 | } 100 | 101 | // Decode decodes the extended handshake message from b. 102 | func (ehm *ExtendedHandshakeMsg) Decode(b []byte) (err error) { 103 | return bencode.DecodeBytes(b, ehm) 104 | } 105 | 106 | // Encode encodes the extended handshake message to b. 107 | func (ehm ExtendedHandshakeMsg) Encode() (b []byte, err error) { 108 | buf := bytes.NewBuffer(make([]byte, 0, 128)) 109 | if err = bencode.NewEncoder(buf).Encode(ehm); err != nil { 110 | b = buf.Bytes() 111 | } 112 | return 113 | } 114 | 115 | // UtMetadataExtendedMsg represents the "ut_metadata" extended message. 116 | type UtMetadataExtendedMsg struct { 117 | MsgType uint8 `bencode:"msg_type"` // BEP 9 118 | Piece int `bencode:"piece"` // BEP 9 119 | 120 | // They are only used by "data" type 121 | TotalSize uint64 `bencode:"total_size,omitempty"` // BEP 9 122 | Data []byte `bencode:"-"` 123 | } 124 | 125 | // EncodeToPayload encodes UtMetadataExtendedMsg to extended payload 126 | // and write the result into buf. 127 | func (um UtMetadataExtendedMsg) EncodeToPayload(buf *bytes.Buffer) (err error) { 128 | if um.MsgType != UtMetadataExtendedMsgTypeData { 129 | um.TotalSize = 0 130 | um.Data = nil 131 | } 132 | 133 | buf.Grow(len(um.Data) + 50) 134 | if err = bencode.NewEncoder(buf).Encode(um); err == nil { 135 | _, err = buf.Write(um.Data) 136 | } 137 | return 138 | } 139 | 140 | // EncodeToBytes is equal to 141 | // 142 | // buf := new(bytes.Buffer) 143 | // err = um.EncodeToPayload(buf) 144 | // return buf.Bytes(), err 145 | func (um UtMetadataExtendedMsg) EncodeToBytes() (b []byte, err error) { 146 | buf := bytes.NewBuffer(make([]byte, 0, 128)) 147 | if err = um.EncodeToPayload(buf); err == nil { 148 | b = buf.Bytes() 149 | } 150 | return 151 | } 152 | 153 | // DecodeFromPayload decodes the extended payload to itself. 154 | func (um *UtMetadataExtendedMsg) DecodeFromPayload(b []byte) (err error) { 155 | dec := bencode.NewDecoder(bytes.NewReader(b)) 156 | if err = dec.Decode(&um); err == nil { 157 | um.Data = b[dec.BytesParsed():] 158 | } 159 | return 160 | } 161 | -------------------------------------------------------------------------------- /peerprotocol/extension_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package peerprotocol 16 | 17 | import ( 18 | "bytes" 19 | "testing" 20 | ) 21 | 22 | func TestCompactIP(t *testing.T) { 23 | ipv4 := CompactIP([]byte{1, 2, 3, 4}) 24 | b, err := ipv4.MarshalBencode() 25 | if err != nil { 26 | t.Fatal(err) 27 | } 28 | 29 | var ip CompactIP 30 | if err = ip.UnmarshalBencode(b); err != nil { 31 | t.Error(err) 32 | } else if ip.String() != "1.2.3.4" { 33 | t.Error(ip) 34 | } 35 | } 36 | 37 | func TestUtMetadataExtendedMsg(t *testing.T) { 38 | buf := new(bytes.Buffer) 39 | data := []byte{0x31, 0x32, 0x33, 0x34, 0x35} 40 | m1 := UtMetadataExtendedMsg{MsgType: 1, Piece: 2, TotalSize: 1024, Data: data} 41 | if err := m1.EncodeToPayload(buf); err != nil { 42 | t.Fatal(err) 43 | } 44 | 45 | msg := Message{Type: MTypeExtended, ExtendedPayload: buf.Bytes()} 46 | m2, err := msg.UtMetadataExtendedMsg() 47 | if err != nil { 48 | t.Fatal(err) 49 | } else if m2.MsgType != 1 || m2.Piece != 2 || m2.TotalSize != 1024 { 50 | t.Error(m2) 51 | } else if !bytes.Equal(m2.Data, data) { 52 | t.Fail() 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /peerprotocol/fastset.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package peerprotocol 16 | 17 | import ( 18 | "crypto/sha1" 19 | "encoding/binary" 20 | "net" 21 | 22 | "github.com/xgfone/go-bt/metainfo" 23 | ) 24 | 25 | // GenerateAllowedFastSet generates some allowed fast set of the torrent file. 26 | // 27 | // Argument: 28 | // 29 | // set: generated piece set, the length of which is the number to be generated. 30 | // sz: the number of pieces in torrent. 31 | // ip: the of the remote peer of the connection. 32 | // infohash: infohash of torrent. 33 | // 34 | // BEP 6 35 | func GenerateAllowedFastSet(set []uint32, sz uint32, ip net.IP, infohash metainfo.Hash) { 36 | if ipv4 := ip.To4(); ipv4 != nil { 37 | ip = ipv4 38 | } 39 | 40 | iplen := len(ip) 41 | x := make([]byte, 20+iplen) 42 | for i, j := 0, iplen-1; i < j; i++ { // (1) compatible with IPv4/IPv6 43 | x[i] = ip[i] & 0xff // (1) 44 | } 45 | // x[iplen-1] = 0 // It is equal to 0 primitively. 46 | copy(x[iplen:], infohash[:]) // (2) 47 | 48 | for cur, k := 0, len(set); cur < k; { 49 | sum := sha1.Sum(x) // (3) 50 | x = sum[:] // (3) 51 | for i := 0; i < 5 && cur < k; i++ { // (4) 52 | j := i * 4 // (5) 53 | y := binary.BigEndian.Uint32(x[j : j+4]) // (6) 54 | index := y % sz // (7) 55 | if !uint32Contains(set, index) { // (8) 56 | set[cur] = index // (9) 57 | cur++ 58 | } 59 | } 60 | } 61 | } 62 | 63 | func uint32Contains(ss []uint32, s uint32) bool { 64 | for _, v := range ss { 65 | if v == s { 66 | return true 67 | } 68 | } 69 | return false 70 | } 71 | -------------------------------------------------------------------------------- /peerprotocol/fastset_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package peerprotocol 16 | 17 | import ( 18 | "net" 19 | "testing" 20 | 21 | "github.com/xgfone/go-bt/metainfo" 22 | ) 23 | 24 | func TestGenerateAllowedFastSet(t *testing.T) { 25 | hexs := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 26 | infohash := metainfo.NewHashFromHexString(hexs) 27 | 28 | sets := make([]uint32, 7) 29 | GenerateAllowedFastSet(sets, 1313, net.ParseIP("80.4.4.200"), infohash) 30 | for i, v := range sets { 31 | switch i { 32 | case 0: 33 | if v != 1059 { 34 | t.Errorf("unknown '%d' at the index '%d'", v, i) 35 | } 36 | case 1: 37 | if v != 431 { 38 | t.Errorf("unknown '%d' at the index '%d'", v, i) 39 | } 40 | case 2: 41 | if v != 808 { 42 | t.Errorf("unknown '%d' at the index '%d'", v, i) 43 | } 44 | case 3: 45 | if v != 1217 { 46 | t.Errorf("unknown '%d' at the index '%d'", v, i) 47 | } 48 | case 4: 49 | if v != 287 { 50 | t.Errorf("unknown '%d' at the index '%d'", v, i) 51 | } 52 | case 5: 53 | if v != 376 { 54 | t.Errorf("unknown '%d' at the index '%d'", v, i) 55 | } 56 | case 6: 57 | if v != 1188 { 58 | t.Errorf("unknown '%d' at the index '%d'", v, i) 59 | } 60 | default: 61 | t.Errorf("unknown '%d' at the index '%d'", v, i) 62 | } 63 | } 64 | 65 | sets = make([]uint32, 9) 66 | GenerateAllowedFastSet(sets, 1313, net.ParseIP("80.4.4.200"), infohash) 67 | for i, v := range sets { 68 | switch i { 69 | case 0: 70 | if v != 1059 { 71 | t.Errorf("unknown '%d' at the index '%d'", v, i) 72 | } 73 | case 1: 74 | if v != 431 { 75 | t.Errorf("unknown '%d' at the index '%d'", v, i) 76 | } 77 | case 2: 78 | if v != 808 { 79 | t.Errorf("unknown '%d' at the index '%d'", v, i) 80 | } 81 | case 3: 82 | if v != 1217 { 83 | t.Errorf("unknown '%d' at the index '%d'", v, i) 84 | } 85 | case 4: 86 | if v != 287 { 87 | t.Errorf("unknown '%d' at the index '%d'", v, i) 88 | } 89 | case 5: 90 | if v != 376 { 91 | t.Errorf("unknown '%d' at the index '%d'", v, i) 92 | } 93 | case 6: 94 | if v != 1188 { 95 | t.Errorf("unknown '%d' at the index '%d'", v, i) 96 | } 97 | case 7: 98 | if v != 353 { 99 | t.Errorf("unknown '%d' at the index '%d'", v, i) 100 | } 101 | case 8: 102 | if v != 508 { 103 | t.Errorf("unknown '%d' at the index '%d'", v, i) 104 | } 105 | default: 106 | t.Errorf("unknown '%d' at the index '%d'", v, i) 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /peerprotocol/handshake.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package peerprotocol 16 | 17 | import ( 18 | "encoding/hex" 19 | "fmt" 20 | "io" 21 | 22 | "github.com/xgfone/go-bt/metainfo" 23 | ) 24 | 25 | var errInvalidProtocolHeader = fmt.Errorf("unexpected peer protocol header string") 26 | 27 | // Predefine some known extension bits. 28 | const ( 29 | ExtensionBitDHT = 0 // BEP 5 30 | ExtensionBitFast = 2 // BEP 6 31 | ExtensionBitExtended = 20 // BEP 10 32 | 33 | ExtensionBitMax = 64 34 | ) 35 | 36 | // ExtensionBits is the reserved bytes to be used by all extensions. 37 | // 38 | // BEP 10: The bit is counted starting at 0 from right to left. 39 | type ExtensionBits [8]byte 40 | 41 | // String returns the hex string format. 42 | func (eb ExtensionBits) String() string { 43 | return hex.EncodeToString(eb[:]) 44 | } 45 | 46 | // Set sets the bit to 1, that's, to set it to be on. 47 | func (eb *ExtensionBits) Set(bit uint) { 48 | eb[7-bit/8] |= 1 << (bit % 8) 49 | } 50 | 51 | // Unset sets the bit to 0, that's, to set it to be off. 52 | func (eb *ExtensionBits) Unset(bit uint) { 53 | eb[7-bit/8] &^= 1 << (bit % 8) 54 | } 55 | 56 | // IsSet reports whether the bit is on. 57 | func (eb ExtensionBits) IsSet(bit uint) (yes bool) { 58 | return eb[7-bit/8]&(1<<(bit%8)) != 0 59 | } 60 | 61 | // IsSupportDHT reports whether ExtensionBitDHT is set. 62 | func (eb ExtensionBits) IsSupportDHT() (yes bool) { 63 | return eb.IsSet(ExtensionBitDHT) 64 | } 65 | 66 | // IsSupportFast reports whether ExtensionBitFast is set. 67 | func (eb ExtensionBits) IsSupportFast() (yes bool) { 68 | return eb.IsSet(ExtensionBitFast) 69 | } 70 | 71 | // IsSupportExtended reports whether ExtensionBitExtended is set. 72 | func (eb ExtensionBits) IsSupportExtended() (yes bool) { 73 | return eb.IsSet(ExtensionBitExtended) 74 | } 75 | 76 | // HandshakeMsg is the message used by the handshake 77 | type HandshakeMsg struct { 78 | ExtensionBits 79 | 80 | PeerID metainfo.Hash 81 | InfoHash metainfo.Hash 82 | } 83 | 84 | // NewHandshakeMsg returns a new HandshakeMsg. 85 | func NewHandshakeMsg(peerID, infoHash metainfo.Hash, es ...ExtensionBits) HandshakeMsg { 86 | var e ExtensionBits 87 | if len(es) > 0 { 88 | e = es[0] 89 | } 90 | return HandshakeMsg{ExtensionBits: e, PeerID: peerID, InfoHash: infoHash} 91 | } 92 | 93 | // Handshake finishes the handshake with the peer. 94 | // 95 | // InfoHash may be ZERO, and it will read it from the peer then send it back 96 | // to the peer. 97 | // 98 | // BEP 3 99 | func Handshake(sock io.ReadWriter, msg HandshakeMsg) (ret HandshakeMsg, err error) { 100 | var read bool 101 | if msg.InfoHash.IsZero() { 102 | if err = getPeerHandshakeMsg(sock, &ret); err != nil { 103 | return 104 | } 105 | read = true 106 | msg.InfoHash = ret.InfoHash 107 | } 108 | 109 | if _, err = io.WriteString(sock, ProtocolHeader); err != nil { 110 | return 111 | } 112 | if _, err = sock.Write(msg.ExtensionBits[:]); err != nil { 113 | return 114 | } 115 | if _, err = sock.Write(msg.InfoHash[:]); err != nil { 116 | return 117 | } 118 | if _, err = sock.Write(msg.PeerID[:]); err != nil { 119 | return 120 | } 121 | 122 | if !read { 123 | err = getPeerHandshakeMsg(sock, &ret) 124 | } 125 | return 126 | } 127 | 128 | func getPeerHandshakeMsg(sock io.Reader, ret *HandshakeMsg) (err error) { 129 | var b [68]byte 130 | if _, err = io.ReadFull(sock, b[:]); err != nil { 131 | return 132 | } else if string(b[:20]) != ProtocolHeader { 133 | return errInvalidProtocolHeader 134 | } 135 | 136 | copy(ret.ExtensionBits[:], b[20:28]) 137 | copy(ret.InfoHash[:], b[28:48]) 138 | copy(ret.PeerID[:], b[48:68]) 139 | return 140 | } 141 | -------------------------------------------------------------------------------- /peerprotocol/message_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package peerprotocol 16 | 17 | import "testing" 18 | 19 | func TestBitField(t *testing.T) { 20 | bf := NewBitFieldFromBools([]bool{ 21 | false, true, false, true, false, true, true, true, 22 | false, false, true, false, false, true, true, false}) 23 | if bf.IsSet(0) { 24 | t.Error(0) 25 | } else if !bf.IsSet(1) { 26 | t.Error(1) 27 | } else if !bf.IsSet(7) { 28 | t.Error(7) 29 | } else if bf.IsSet(8) { 30 | t.Error(8) 31 | } else if bf.IsSet(15) { 32 | t.Error(15) 33 | } 34 | 35 | bf.Set(9) 36 | if !bf.IsSet(9) { 37 | t.Error(9) 38 | } 39 | 40 | bf.Unset(10) 41 | if bf.IsSet(10) { 42 | t.Error(10) 43 | } 44 | 45 | bs := bf.Bools() 46 | if len(bs) != 16 { 47 | t.Fatal(bs) 48 | } else if !bs[9] { 49 | t.Error(9) 50 | } else if bs[10] { 51 | t.Error(10) 52 | } 53 | 54 | bf = NewBitField(16, true) 55 | if !bf.IsSet(0) || !bf.IsSet(1) || !bf.IsSet(2) || !bf.IsSet(3) || 56 | !bf.IsSet(4) || !bf.IsSet(5) || !bf.IsSet(6) || !bf.IsSet(7) { 57 | t.Error(bf) 58 | } 59 | } 60 | 61 | func TestPieces(t *testing.T) { 62 | ps := Pieces{2, 3, 4, 5} 63 | ps = ps.Append(1) 64 | if len(ps) != 5 || ps[0] != 1 { 65 | t.Fatal(ps) 66 | } 67 | 68 | ps = ps.Append(5) 69 | if len(ps) != 5 { 70 | t.Fatal(ps) 71 | } 72 | 73 | ps = ps.Remove(6) 74 | if len(ps) != 5 { 75 | t.Fatal(ps) 76 | } 77 | 78 | ps = ps.Remove(3) 79 | if len(ps) != 4 || ps[0] != 1 || ps[1] != 2 || ps[2] != 4 || ps[3] != 5 { 80 | t.Fatal(ps) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /peerprotocol/noop_handler.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package peerprotocol 16 | 17 | var _ Handler = NoopHandler{} 18 | var _ Bep3Handler = NoopBep3Handler{} 19 | var _ Bep5Handler = NoopBep5Handler{} 20 | var _ Bep6Handler = NoopBep6Handler{} 21 | var _ Bep10Handler = NoopBep10Handler{} 22 | 23 | // NoopHandler implements the interface Handler to do nothing, 24 | // which is used to be embedded into other structure to not implement 25 | // the noop interface methods. 26 | type NoopHandler struct{} 27 | 28 | // OnClose implements the interface Bep3Handler#OnClose. 29 | func (NoopHandler) OnClose(*PeerConn) {} 30 | 31 | // OnHandShake implements the interface Bep3Handler#OnHandShake. 32 | func (NoopHandler) OnHandShake(*PeerConn) error { return nil } 33 | 34 | // OnMessage implements the interface Bep3Handler#OnMessage. 35 | func (NoopHandler) OnMessage(*PeerConn, Message) error { return nil } 36 | 37 | // NoopBep3Handler implements the interface Bep3Handler to do nothing, 38 | // which is used to be embedded into other structure to not implement 39 | // the noop interface methods. 40 | type NoopBep3Handler struct{} 41 | 42 | // Unchoke implements the interface Bep3Handler#Unchoke. 43 | func (NoopBep3Handler) Unchoke(*PeerConn) error { return nil } 44 | 45 | // Request implements the interface Bep3Handler#Request. 46 | func (NoopBep3Handler) Request(*PeerConn, uint32, uint32, uint32) error { return nil } 47 | 48 | // Have implements the interface Bep3Handler#Have. 49 | func (NoopBep3Handler) Have(*PeerConn, uint32) error { return nil } 50 | 51 | // BitField implements the interface Bep3Handler#Bitfield. 52 | func (NoopBep3Handler) BitField(*PeerConn, BitField) error { return nil } 53 | 54 | // Piece implements the interface Bep3Handler#Piece. 55 | func (NoopBep3Handler) Piece(*PeerConn, uint32, uint32, []byte) error { return nil } 56 | 57 | // Choke implements the interface Bep3Handler#Choke. 58 | func (NoopBep3Handler) Choke(pc *PeerConn) error { return nil } 59 | 60 | // Interested implements the interface Bep3Handler#Interested. 61 | func (NoopBep3Handler) Interested(pc *PeerConn) error { return nil } 62 | 63 | // NotInterested implements the interface Bep3Handler#NotInterested. 64 | func (NoopBep3Handler) NotInterested(pc *PeerConn) error { return nil } 65 | 66 | // Cancel implements the interface Bep3Handler#Cancel. 67 | func (NoopBep3Handler) Cancel(*PeerConn, uint32, uint32, uint32) error { return nil } 68 | 69 | // NoopBep5Handler implements the interface Bep5Handler to do nothing, 70 | // which is used to be embedded into other structure to not implement 71 | // the noop interface methods. 72 | type NoopBep5Handler struct{} 73 | 74 | // Port implements the interface Bep5Handler#Port. 75 | func (NoopBep5Handler) Port(*PeerConn, uint16) error { return nil } 76 | 77 | // NoopBep6Handler implements the interface Bep6Handler to do nothing, 78 | // which is used to be embedded into other structure to not implement 79 | // the noop interface methods. 80 | type NoopBep6Handler struct{} 81 | 82 | // HaveAll implements the interface Bep6Handler#HaveAll. 83 | func (NoopBep6Handler) HaveAll(*PeerConn) error { return nil } 84 | 85 | // HaveNone implements the interface Bep6Handler#HaveNone. 86 | func (NoopBep6Handler) HaveNone(*PeerConn) error { return nil } 87 | 88 | // Suggest implements the interface Bep6Handler#Suggest. 89 | func (NoopBep6Handler) Suggest(*PeerConn, uint32) error { return nil } 90 | 91 | // AllowedFast implements the interface Bep6Handler#AllowedFast. 92 | func (NoopBep6Handler) AllowedFast(*PeerConn, uint32) error { return nil } 93 | 94 | // Reject implements the interface Bep6Handler#Reject. 95 | func (NoopBep6Handler) Reject(*PeerConn, uint32, uint32, uint32) error { return nil } 96 | 97 | // NoopBep10Handler implements the interface Bep10Handler to do nothing, 98 | // which is used to be embedded into other structure to not implement 99 | // the noop interface methods. 100 | type NoopBep10Handler struct{} 101 | 102 | // OnExtHandShake implements the interface Bep10Handler#OnExtHandShake. 103 | func (NoopBep10Handler) OnExtHandShake(*PeerConn) error { return nil } 104 | 105 | // OnPayload implements the interface Bep10Handler#OnPayload. 106 | func (NoopBep10Handler) OnPayload(*PeerConn, uint8, []byte) error { return nil } 107 | -------------------------------------------------------------------------------- /peerprotocol/protocol.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package peerprotocol 16 | 17 | import "fmt" 18 | 19 | // ProtocolHeader is the BT protocal prefix. 20 | // 21 | // BEP 3 22 | const ProtocolHeader = "\x13BitTorrent protocol" 23 | 24 | // Predefine some message types. 25 | const ( 26 | // BEP 3 27 | MTypeChoke MessageType = 0 28 | MTypeUnchoke MessageType = 1 29 | MTypeInterested MessageType = 2 30 | MTypeNotInterested MessageType = 3 31 | MTypeHave MessageType = 4 32 | MTypeBitField MessageType = 5 33 | MTypeRequest MessageType = 6 34 | MTypePiece MessageType = 7 35 | MTypeCancel MessageType = 8 36 | 37 | // BEP 5 38 | MTypePort MessageType = 9 39 | 40 | // BEP 6 - Fast extension 41 | MTypeSuggest MessageType = 0x0d // 13 42 | MTypeHaveAll MessageType = 0x0e // 14 43 | MTypeHaveNone MessageType = 0x0f // 15 44 | MTypeReject MessageType = 0x10 // 16 45 | MTypeAllowedFast MessageType = 0x11 // 17 46 | 47 | // BEP 10 48 | MTypeExtended MessageType = 20 49 | ) 50 | 51 | // MessageType is used to represent the message type. 52 | type MessageType byte 53 | 54 | func (mt MessageType) String() string { 55 | switch mt { 56 | case MTypeChoke: 57 | return "Choke" 58 | case MTypeUnchoke: 59 | return "Unchoke" 60 | case MTypeInterested: 61 | return "Interested" 62 | case MTypeNotInterested: 63 | return "NotInterested" 64 | case MTypeHave: 65 | return "Have" 66 | case MTypeBitField: 67 | return "Bitfield" 68 | case MTypeRequest: 69 | return "Request" 70 | case MTypePiece: 71 | return "Piece" 72 | case MTypeCancel: 73 | return "Cancel" 74 | case MTypePort: 75 | return "Port" 76 | case MTypeSuggest: 77 | return "Suggest" 78 | case MTypeHaveAll: 79 | return "HaveAll" 80 | case MTypeHaveNone: 81 | return "HaveNone" 82 | case MTypeReject: 83 | return "Reject" 84 | case MTypeAllowedFast: 85 | return "AllowedFast" 86 | case MTypeExtended: 87 | return "Extended" 88 | } 89 | return fmt.Sprintf("MessageType(%d)", mt) 90 | } 91 | 92 | // FastExtension reports whether the message type is fast extension. 93 | func (mt MessageType) FastExtension() bool { 94 | return mt >= MTypeSuggest && mt <= MTypeAllowedFast 95 | } 96 | -------------------------------------------------------------------------------- /peerprotocol/server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package peerprotocol 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | "log" 21 | "net" 22 | "strings" 23 | "time" 24 | 25 | "github.com/xgfone/go-bt/metainfo" 26 | ) 27 | 28 | // Handler is used to handle the incoming peer connection. 29 | type Handler interface { 30 | // OnHandShake is used to check whether the handshake extension is acceptable. 31 | OnHandShake(conn *PeerConn) error 32 | 33 | // OnMessage is used to handle the incoming peer message. 34 | // 35 | // If requires, it should write the response to the peer. 36 | OnMessage(conn *PeerConn, msg Message) error 37 | 38 | // OnClose is called when the connection is closed, which may be used 39 | // to do some cleaning work by the handler. 40 | OnClose(conn *PeerConn) 41 | } 42 | 43 | // Config is used to configure the server. 44 | type Config struct { 45 | // ExtBits is used to handshake with the client. 46 | ExtBits ExtensionBits 47 | 48 | // MaxLength is used to limit the maximum number of the message body. 49 | // 50 | // 0 represents no limit, and the default is 262144, that's, 256KB. 51 | MaxLength uint32 52 | 53 | // Timeout is used to control the timeout of the read/write the message. 54 | // 55 | // The default is 0, which represents no timeout. 56 | Timeout time.Duration 57 | 58 | // ErrorLog is used to log the error. 59 | ErrorLog func(format string, args ...interface{}) // Default: log.Printf 60 | 61 | // HandleMessage is used to handle the incoming message. So you can 62 | // customize it to add the request queue. 63 | // 64 | // The default handler is to forward to pc.HandleMessage(msg, handler). 65 | HandleMessage func(pc *PeerConn, msg Message, handler Handler) error 66 | } 67 | 68 | func (c *Config) set(conf *Config) { 69 | if conf != nil { 70 | *c = *conf 71 | } 72 | 73 | if c.MaxLength == 0 { 74 | c.MaxLength = 262144 // 256KB 75 | } 76 | if c.ErrorLog == nil { 77 | c.ErrorLog = log.Printf 78 | } 79 | if c.HandleMessage == nil { 80 | c.HandleMessage = func(pc *PeerConn, m Message, h Handler) error { 81 | return pc.HandleMessage(m, h) 82 | } 83 | } 84 | } 85 | 86 | // Server is used to implement the peer protocol server. 87 | type Server struct { 88 | net.Listener 89 | 90 | ID metainfo.Hash // Required 91 | Handler Handler // Required 92 | Config Config // Optional 93 | } 94 | 95 | // NewServerByListen returns a new Server by listening on the address. 96 | func NewServerByListen(network, address string, id metainfo.Hash, h Handler, c *Config) (*Server, error) { 97 | ln, err := net.Listen(network, address) 98 | if err != nil { 99 | return nil, err 100 | } 101 | return NewServer(ln, id, h, c), nil 102 | } 103 | 104 | // NewServer returns a new Server. 105 | func NewServer(ln net.Listener, id metainfo.Hash, h Handler, c *Config) *Server { 106 | if id.IsZero() { 107 | panic("the peer node id must not be empty") 108 | } 109 | 110 | var conf Config 111 | conf.set(c) 112 | return &Server{Listener: ln, ID: id, Handler: h, Config: conf} 113 | } 114 | 115 | // Run starts the peer protocol server. 116 | func (s *Server) Run() { 117 | s.Config.set(nil) 118 | for { 119 | conn, err := s.Listener.Accept() 120 | if err != nil { 121 | if !strings.Contains(err.Error(), "closed") { 122 | s.Config.ErrorLog("fail to accept new connection: %s", err) 123 | } 124 | return 125 | } 126 | go s.handleConn(conn) 127 | } 128 | } 129 | 130 | func (s *Server) handleConn(conn net.Conn) { 131 | pc := &PeerConn{ 132 | ID: s.ID, 133 | Conn: conn, 134 | ExtBits: s.Config.ExtBits, 135 | Timeout: s.Config.Timeout, 136 | MaxLength: s.Config.MaxLength, 137 | Choked: true, 138 | PeerChoked: true, 139 | } 140 | 141 | if err := s.handlePeerMessage(pc); err != nil { 142 | s.Config.ErrorLog(err.Error()) 143 | } 144 | } 145 | 146 | func (s *Server) handlePeerMessage(pc *PeerConn) (err error) { 147 | defer pc.Close() 148 | if err = pc.Handshake(); err != nil { 149 | return fmt.Errorf("fail to handshake with '%s': %s", pc.RemoteAddr().String(), err) 150 | } else if err = s.Handler.OnHandShake(pc); err != nil { 151 | return fmt.Errorf("handshake error with '%s': %s", pc.RemoteAddr().String(), err) 152 | } 153 | 154 | defer s.Handler.OnClose(pc) 155 | return s.loopRun(pc, s.Handler) 156 | } 157 | 158 | // LoopRun loops running Read-Handle message. 159 | func (s *Server) loopRun(pc *PeerConn, handler Handler) error { 160 | for { 161 | msg, err := pc.ReadMsg() 162 | switch err { 163 | case nil: 164 | case io.EOF: 165 | return nil 166 | default: 167 | s := err.Error() 168 | if strings.Contains(s, "closed") { 169 | return nil 170 | } 171 | return fmt.Errorf("fail to decode the message from '%s': %s", 172 | pc.RemoteAddr().String(), s) 173 | } 174 | 175 | if err = s.Config.HandleMessage(pc, msg, handler); err != nil { 176 | return fmt.Errorf("fail to handle peer message from '%s': %s", 177 | pc.RemoteAddr().String(), err) 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /tracker/get_peers.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020~2023 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package tracker 16 | 17 | import ( 18 | "context" 19 | 20 | "github.com/xgfone/go-bt/metainfo" 21 | ) 22 | 23 | // GetPeers gets the peers from the tracker. 24 | func GetPeers(ctx context.Context, tracker string, nodeID, infoHash metainfo.Hash, totalLength int64) (AnnounceResponse, error) { 25 | client, err := NewClient(tracker, nodeID, nil) 26 | if err != nil { 27 | return AnnounceResponse{}, err 28 | } 29 | 30 | return client.Announce(ctx, AnnounceRequest{ 31 | Left: totalLength, 32 | InfoHash: infoHash, 33 | Port: 6881, 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /tracker/httptracker/go1.13.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build go1.13 16 | // +build go1.13 17 | 18 | package httptracker 19 | 20 | import ( 21 | "context" 22 | "io" 23 | "net/http" 24 | ) 25 | 26 | // NewRequestWithContext returns a new Request given a method, URL, and optional body. 27 | func NewRequestWithContext(c context.Context, method, url string, body io.Reader) (*http.Request, error) { 28 | return http.NewRequestWithContext(c, method, url, body) 29 | } 30 | -------------------------------------------------------------------------------- /tracker/httptracker/go1.13_compat.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !go1.13 16 | // +build !go1.13 17 | 18 | package httptracker 19 | 20 | import ( 21 | "context" 22 | "io" 23 | "net/http" 24 | ) 25 | 26 | // NewRequestWithContext returns a new Request given a method, URL, and optional body. 27 | func NewRequestWithContext(c context.Context, method, url string, body io.Reader) (*http.Request, error) { 28 | req, err := http.NewRequest(method, url, body) 29 | if err != nil { 30 | return nil, nil 31 | } 32 | return req.WithContext(c), nil 33 | } 34 | -------------------------------------------------------------------------------- /tracker/httptracker/http_peer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package httptracker 16 | 17 | import ( 18 | "errors" 19 | "net" 20 | 21 | "github.com/xgfone/go-bt/bencode" 22 | "github.com/xgfone/go-bt/metainfo" 23 | ) 24 | 25 | var errInvalidPeer = errors.New("invalid bt peer information format") 26 | 27 | // Peer is a tracker peer. 28 | type Peer struct { 29 | ID string `bencode:"peer id"` // BEP 3, the peer's self-selected ID. 30 | IP string `bencode:"ip"` // BEP 3, an IP address or dns name. 31 | Port uint16 `bencode:"port"` // BEP 3 32 | } 33 | 34 | var ( 35 | _ bencode.Marshaler = new(Peers) 36 | _ bencode.Unmarshaler = new(Peers) 37 | ) 38 | 39 | // Peers is a set of the peers. 40 | type Peers []Peer 41 | 42 | // UnmarshalBencode implements the interface bencode.Unmarshaler. 43 | func (ps *Peers) UnmarshalBencode(b []byte) (err error) { 44 | var v interface{} 45 | if err = bencode.DecodeBytes(b, &v); err != nil { 46 | return 47 | } 48 | 49 | switch vs := v.(type) { 50 | case string: // BEP 23 51 | var addrs metainfo.CompactIPv4Addrs 52 | if err = addrs.UnmarshalBinary([]byte(vs)); err != nil { 53 | return err 54 | } 55 | 56 | peers := make(Peers, len(addrs)) 57 | for i, addr := range addrs { 58 | peers[i] = Peer{IP: addr.IP.String(), Port: addr.Port} 59 | } 60 | *ps = peers 61 | 62 | case []interface{}: // BEP 3 63 | peers := make(Peers, len(vs)) 64 | for i, p := range vs { 65 | m, ok := p.(map[string]interface{}) 66 | if !ok { 67 | return errInvalidPeer 68 | } 69 | 70 | pid, ok := m["peer id"].(string) 71 | if !ok { 72 | return errInvalidPeer 73 | } 74 | 75 | ip, ok := m["ip"].(string) 76 | if !ok { 77 | return errInvalidPeer 78 | } 79 | 80 | port, ok := m["port"].(int64) 81 | if !ok { 82 | return errInvalidPeer 83 | } 84 | 85 | peers[i] = Peer{ID: pid, IP: ip, Port: uint16(port)} 86 | } 87 | *ps = peers 88 | 89 | default: 90 | return errInvalidPeer 91 | } 92 | return 93 | } 94 | 95 | // MarshalBencode implements the interface bencode.Marshaler. 96 | func (ps Peers) MarshalBencode() (b []byte, err error) { 97 | // BEP 23 98 | if b, err = ps.marshalCompactBencode(); err == nil { 99 | return 100 | } 101 | 102 | // BEP 3 103 | return bencode.EncodeBytes([]Peer(ps)) 104 | } 105 | 106 | func (ps Peers) marshalCompactBencode() (b []byte, err error) { 107 | addrs := make(metainfo.CompactIPv4Addrs, len(ps)) 108 | for i, p := range ps { 109 | ip := net.ParseIP(p.IP).To4() 110 | if ip == nil { 111 | return nil, errInvalidPeer 112 | } 113 | addrs[i] = metainfo.CompactAddr{IP: ip, Port: p.Port} 114 | } 115 | return addrs.MarshalBencode() 116 | } 117 | 118 | var ( 119 | _ bencode.Marshaler = new(Peers6) 120 | _ bencode.Unmarshaler = new(Peers6) 121 | ) 122 | 123 | // Peers6 is a set of the peers for IPv6 in the compact case. 124 | // 125 | // BEP 7 126 | type Peers6 []Peer 127 | 128 | // UnmarshalBencode implements the interface bencode.Unmarshaler. 129 | func (ps *Peers6) UnmarshalBencode(b []byte) (err error) { 130 | var s string 131 | if err = bencode.DecodeBytes(b, &s); err != nil { 132 | return 133 | } 134 | 135 | var addrs metainfo.CompactIPv6Addrs 136 | if err = addrs.UnmarshalBinary([]byte(s)); err != nil { 137 | return err 138 | } 139 | 140 | peers := make(Peers6, len(addrs)) 141 | for i, addr := range addrs { 142 | peers[i] = Peer{IP: addr.IP.String(), Port: addr.Port} 143 | } 144 | *ps = peers 145 | 146 | return 147 | } 148 | 149 | // MarshalBencode implements the interface bencode.Marshaler. 150 | func (ps Peers6) MarshalBencode() (b []byte, err error) { 151 | addrs := make(metainfo.CompactIPv6Addrs, len(ps)) 152 | for i, p := range ps { 153 | ip := net.ParseIP(p.IP) 154 | if ip == nil { 155 | return nil, errInvalidPeer 156 | } 157 | addrs[i] = metainfo.CompactAddr{IP: ip, Port: p.Port} 158 | } 159 | return addrs.MarshalBencode() 160 | } 161 | -------------------------------------------------------------------------------- /tracker/httptracker/http_peer_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package httptracker 16 | 17 | import ( 18 | "reflect" 19 | "testing" 20 | ) 21 | 22 | func TestPeers(t *testing.T) { 23 | peers := Peers{ 24 | {IP: "1.1.1.1", Port: 80}, 25 | {IP: "2.2.2.2", Port: 81}, 26 | } 27 | 28 | b, err := peers.MarshalBencode() 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | 33 | var ps Peers 34 | if err = ps.UnmarshalBencode(b); err != nil { 35 | t.Fatal(err) 36 | } else if !reflect.DeepEqual(ps, peers) { 37 | t.Errorf("%v != %v", ps, peers) 38 | } 39 | 40 | /// For BEP 23 41 | peers = Peers{ 42 | {IP: "1.1.1.1", Port: 80}, 43 | {IP: "2.2.2.2", Port: 81}, 44 | } 45 | 46 | b, err = peers.MarshalBencode() 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | 51 | if err = ps.UnmarshalBencode(b); err != nil { 52 | t.Fatal(err) 53 | } else if !reflect.DeepEqual(ps, peers) { 54 | t.Errorf("%v != %v", ps, peers) 55 | } 56 | } 57 | 58 | func TestPeers6(t *testing.T) { 59 | peers := Peers6{ 60 | {IP: "fe80::5054:ff:fef0:1ab", Port: 80}, 61 | {IP: "fe80::5054:ff:fe29:205d", Port: 81}, 62 | } 63 | 64 | b, err := peers.MarshalBencode() 65 | if err != nil { 66 | t.Fatal(err) 67 | } 68 | 69 | var ps Peers6 70 | if err = ps.UnmarshalBencode(b); err != nil { 71 | t.Fatal(err) 72 | } else if !reflect.DeepEqual(ps, peers) { 73 | t.Errorf("%v != %v", ps, peers) 74 | t.Error(string(b)) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /tracker/httptracker/http_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package httptracker 16 | 17 | import ( 18 | "reflect" 19 | "testing" 20 | 21 | "github.com/xgfone/go-bt/metainfo" 22 | ) 23 | 24 | func TestHTTPAnnounceRequest(t *testing.T) { 25 | infohash := metainfo.NewRandomHash() 26 | peerid := metainfo.NewRandomHash() 27 | v1 := AnnounceRequest{ 28 | InfoHash: infohash, 29 | PeerID: peerid, 30 | Uploaded: 789, 31 | Downloaded: 456, 32 | Left: 123, 33 | Port: 80, 34 | Event: 123, 35 | Compact: true, 36 | } 37 | vs := v1.ToQuery() 38 | 39 | var v2 AnnounceRequest 40 | if err := v2.FromQuery(vs); err != nil { 41 | t.Fatal(err) 42 | } 43 | 44 | if v2.InfoHash != infohash { 45 | t.Error(v2.InfoHash) 46 | } 47 | if v2.PeerID != peerid { 48 | t.Error(v2.PeerID) 49 | } 50 | if v2.Uploaded != 789 { 51 | t.Error(v2.Uploaded) 52 | } 53 | if v2.Downloaded != 456 { 54 | t.Error(v2.Downloaded) 55 | } 56 | if v2.Left != 123 { 57 | t.Error(v2.Left) 58 | } 59 | if v2.Port != 80 { 60 | t.Error(v2.Port) 61 | } 62 | if v2.Event != 123 { 63 | t.Error(v2.Event) 64 | } 65 | if !v2.Compact { 66 | t.Error(v2.Compact) 67 | } 68 | 69 | if !reflect.DeepEqual(v1, v2) { 70 | t.Errorf("%v != %v", v1, v2) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tracker/tracker_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020~2023 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package tracker 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | "fmt" 21 | "log" 22 | "net" 23 | "time" 24 | 25 | "github.com/xgfone/go-bt/metainfo" 26 | "github.com/xgfone/go-bt/tracker/udptracker" 27 | ) 28 | 29 | type testHandler struct{} 30 | 31 | func (testHandler) OnConnect(raddr *net.UDPAddr) (err error) { return } 32 | func (testHandler) OnAnnounce(raddr *net.UDPAddr, req udptracker.AnnounceRequest) (r udptracker.AnnounceResponse, err error) { 33 | if req.Port != 80 { 34 | err = errors.New("port is not 80") 35 | return 36 | } 37 | 38 | if len(req.Exts) > 0 { 39 | for i, ext := range req.Exts { 40 | switch ext.Type { 41 | case udptracker.URLData: 42 | fmt.Printf("Extensions[%d]: URLData(%s)\n", i, string(ext.Data)) 43 | default: 44 | fmt.Printf("Extensions[%d]: %s\n", i, ext.Type.String()) 45 | } 46 | } 47 | } 48 | 49 | r = udptracker.AnnounceResponse{ 50 | Interval: 1, 51 | Leechers: 2, 52 | Seeders: 3, 53 | Addresses: []metainfo.CompactAddr{{IP: net.ParseIP("127.0.0.1"), Port: 8000}}, 54 | } 55 | return 56 | } 57 | func (testHandler) OnScrap(raddr *net.UDPAddr, infohashes []metainfo.Hash) (rs []udptracker.ScrapeResponse, err error) { 58 | rs = make([]udptracker.ScrapeResponse, len(infohashes)) 59 | for i := range infohashes { 60 | rs[i] = udptracker.ScrapeResponse{ 61 | Seeders: uint32(i)*10 + 1, 62 | Leechers: uint32(i)*10 + 2, 63 | Completed: uint32(i)*10 + 3, 64 | } 65 | } 66 | return 67 | } 68 | 69 | func ExampleClient() { 70 | // Start the UDP tracker server 71 | sconn, err := net.ListenPacket("udp4", "127.0.0.1:8000") 72 | if err != nil { 73 | log.Fatal(err) 74 | } 75 | server := udptracker.NewServer(sconn, testHandler{}, 0) 76 | defer server.Close() 77 | go server.Run() 78 | 79 | // Wait for the server to be started 80 | time.Sleep(time.Second) 81 | 82 | // Create a client and dial to the UDP tracker server. 83 | client, err := NewClient("udp://127.0.0.1:8000/path?a=1&b=2", metainfo.Hash{}, nil) 84 | if err != nil { 85 | log.Fatal(err) 86 | } 87 | 88 | // Send the ANNOUNCE request to the UDP tracker server, 89 | // and get the ANNOUNCE response. 90 | req := AnnounceRequest{InfoHash: metainfo.NewRandomHash(), IP: net.ParseIP("127.0.0.1"), Port: 80} 91 | resp, err := client.Announce(context.Background(), req) 92 | if err != nil { 93 | log.Fatal(err) 94 | } 95 | 96 | fmt.Printf("Interval: %d\n", resp.Interval) 97 | fmt.Printf("Leechers: %d\n", resp.Leechers) 98 | fmt.Printf("Seeders: %d\n", resp.Seeders) 99 | for i, addr := range resp.Addresses { 100 | fmt.Printf("Address[%d].IP: %s\n", i, addr.Host) 101 | fmt.Printf("Address[%d].Port: %d\n", i, addr.Port) 102 | } 103 | 104 | // Send the SCRAPE request to the UDP tracker server, 105 | // and get the SCRAPE respsone. 106 | h1 := metainfo.Hash{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} 107 | h2 := metainfo.Hash{2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2} 108 | rs, err := client.Scrape(context.Background(), []metainfo.Hash{h1, h2}) 109 | if err != nil { 110 | log.Fatal(err) 111 | } else if len(rs) != 2 { 112 | log.Fatalf("%+v", rs) 113 | } 114 | 115 | for i, r := range rs { 116 | fmt.Printf("%s.Seeders: %d\n", i.HexString(), r.Seeders) 117 | fmt.Printf("%s.Leechers: %d\n", i.HexString(), r.Leechers) 118 | fmt.Printf("%s.Completed: %d\n", i.HexString(), r.Completed) 119 | } 120 | 121 | // Unordered output: 122 | // Extensions[0]: URLData(/path?a=1&b=2) 123 | // Interval: 1 124 | // Leechers: 2 125 | // Seeders: 3 126 | // Address[0].IP: 127.0.0.1 127 | // Address[0].Port: 8000 128 | // 0101010101010101010101010101010101010101.Seeders: 1 129 | // 0101010101010101010101010101010101010101.Leechers: 2 130 | // 0101010101010101010101010101010101010101.Completed: 3 131 | // 0202020202020202020202020202020202020202.Seeders: 11 132 | // 0202020202020202020202020202020202020202.Leechers: 12 133 | // 0202020202020202020202020202020202020202.Completed: 13 134 | } 135 | -------------------------------------------------------------------------------- /tracker/udptracker/udp.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020~2023 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package udptracker implements the tracker protocol based on UDP. 16 | // 17 | // You can use the package to implement a UDP tracker server to track the 18 | // information that other peers upload or download the file, or to create 19 | // a UDP tracker client to communicate with the UDP tracker server. 20 | package udptracker 21 | 22 | import ( 23 | "bytes" 24 | "encoding/binary" 25 | "fmt" 26 | "net" 27 | 28 | "github.com/xgfone/go-bt/metainfo" 29 | ) 30 | 31 | const maxBufSize = 2048 32 | 33 | // ProtocolID is magic constant for the udp tracker connection. 34 | // 35 | // BEP 15 36 | const ProtocolID = uint64(0x41727101980) 37 | 38 | // Predefine some actions. 39 | // 40 | // BEP 15 41 | const ( 42 | ActionConnect = uint32(0) 43 | ActionAnnounce = uint32(1) 44 | ActionScrape = uint32(2) 45 | ActionError = uint32(3) 46 | ) 47 | 48 | // AnnounceRequest represents the announce request used by UDP tracker. 49 | // 50 | // BEP 15 51 | type AnnounceRequest struct { 52 | InfoHash metainfo.Hash 53 | PeerID metainfo.Hash 54 | 55 | Downloaded int64 56 | Left int64 57 | Uploaded int64 58 | Event uint32 59 | 60 | Key int32 61 | NumWant int32 // -1 for default and use -1 instead if 0 62 | Port uint16 63 | 64 | Exts []Extension // BEP 41 65 | } 66 | 67 | // DecodeFrom decodes the request from b. 68 | func (r *AnnounceRequest) DecodeFrom(b []byte) { 69 | r.InfoHash = metainfo.NewHash(b[0:20]) 70 | r.PeerID = metainfo.NewHash(b[20:40]) 71 | r.Downloaded = int64(binary.BigEndian.Uint64(b[40:48])) 72 | r.Left = int64(binary.BigEndian.Uint64(b[48:56])) 73 | r.Uploaded = int64(binary.BigEndian.Uint64(b[56:64])) 74 | r.Event = binary.BigEndian.Uint32(b[64:68]) 75 | 76 | // ignore b[68:72] // 4 bytes 77 | 78 | r.Key = int32(binary.BigEndian.Uint32(b[72:76])) 79 | r.NumWant = int32(binary.BigEndian.Uint32(b[76:80])) 80 | r.Port = binary.BigEndian.Uint16(b[80:82]) 81 | 82 | b = b[82:] 83 | for len(b) > 0 { 84 | var ext Extension 85 | parsed := ext.DecodeFrom(b) 86 | r.Exts = append(r.Exts, ext) 87 | b = b[parsed:] 88 | } 89 | } 90 | 91 | // EncodeTo encodes the request to buf. 92 | func (r AnnounceRequest) EncodeTo(buf *bytes.Buffer) { 93 | if r.NumWant <= 0 { 94 | r.NumWant = -1 95 | } 96 | 97 | buf.Grow(82) 98 | buf.Write(r.InfoHash[:]) // 20: 16 - 36 99 | buf.Write(r.PeerID[:]) // 20: 36 - 56 100 | 101 | binary.Write(buf, binary.BigEndian, r.Downloaded) // 8: 56 - 64 102 | binary.Write(buf, binary.BigEndian, r.Left) // 8: 64 - 72 103 | binary.Write(buf, binary.BigEndian, r.Uploaded) // 8: 72 - 80 104 | binary.Write(buf, binary.BigEndian, r.Event) // 4: 80 - 84 105 | binary.Write(buf, binary.BigEndian, uint32(0)) // 4: 84 - 88 106 | 107 | binary.Write(buf, binary.BigEndian, r.Key) // 4: 88 - 92 108 | binary.Write(buf, binary.BigEndian, r.NumWant) // 4: 92 - 96 109 | binary.Write(buf, binary.BigEndian, r.Port) // 2: 96 - 98 110 | 111 | for _, ext := range r.Exts { // N: 98 - 112 | ext.EncodeTo(buf) 113 | } 114 | } 115 | 116 | // AnnounceResponse represents the announce response used by UDP tracker. 117 | // 118 | // BEP 15 119 | type AnnounceResponse struct { 120 | Interval uint32 121 | Leechers uint32 122 | Seeders uint32 123 | Addresses []metainfo.CompactAddr 124 | } 125 | 126 | // EncodeTo encodes the response to buf. 127 | func (r AnnounceResponse) EncodeTo(buf *bytes.Buffer, ipv4 bool) { 128 | buf.Grow(12 + len(r.Addresses)*18) 129 | binary.Write(buf, binary.BigEndian, r.Interval) 130 | binary.Write(buf, binary.BigEndian, r.Leechers) 131 | binary.Write(buf, binary.BigEndian, r.Seeders) 132 | for i, addr := range r.Addresses { 133 | if ipv4 { 134 | addr.IP = addr.IP.To4() 135 | } else { 136 | addr.IP = addr.IP.To16() 137 | } 138 | if len(addr.IP) == 0 { 139 | panic(fmt.Errorf("invalid ip '%s'", r.Addresses[i].IP.String())) 140 | } 141 | addr.WriteBinary(buf) 142 | } 143 | } 144 | 145 | // DecodeFrom decodes the response from b. 146 | func (r *AnnounceResponse) DecodeFrom(b []byte, ipv4 bool) { 147 | r.Interval = binary.BigEndian.Uint32(b[:4]) // 4: 8 - 12 148 | r.Leechers = binary.BigEndian.Uint32(b[4:8]) // 4: 12 - 16 149 | r.Seeders = binary.BigEndian.Uint32(b[8:12]) // 4: 16 - 20 150 | 151 | b = b[12:] // N*(6|18): 20 - 152 | iplen := net.IPv6len 153 | if ipv4 { 154 | iplen = net.IPv4len 155 | } 156 | 157 | _len := len(b) 158 | step := iplen + 2 159 | r.Addresses = make([]metainfo.CompactAddr, 0, _len/step) 160 | for i := step; i <= _len; i += step { 161 | var addr metainfo.CompactAddr 162 | if err := addr.UnmarshalBinary(b[i-step : i]); err != nil { 163 | panic(err) 164 | } 165 | r.Addresses = append(r.Addresses, addr) 166 | } 167 | } 168 | 169 | // ScrapeResponse represents the UDP SCRAPE response. 170 | // 171 | // BEP 15 172 | type ScrapeResponse struct { 173 | Seeders uint32 174 | Leechers uint32 175 | Completed uint32 176 | } 177 | 178 | // EncodeTo encodes the response to buf. 179 | func (r ScrapeResponse) EncodeTo(buf *bytes.Buffer) { 180 | binary.Write(buf, binary.BigEndian, r.Seeders) 181 | binary.Write(buf, binary.BigEndian, r.Completed) 182 | binary.Write(buf, binary.BigEndian, r.Leechers) 183 | } 184 | 185 | // DecodeFrom decodes the response from b. 186 | func (r *ScrapeResponse) DecodeFrom(b []byte) { 187 | r.Seeders = binary.BigEndian.Uint32(b[:4]) 188 | r.Completed = binary.BigEndian.Uint32(b[4:8]) 189 | r.Leechers = binary.BigEndian.Uint32(b[8:12]) 190 | } 191 | 192 | // Predefine some UDP extension types. 193 | // 194 | // BEP 41 195 | const ( 196 | EndOfOptions ExtensionType = iota 197 | Nop 198 | URLData 199 | ) 200 | 201 | // NewEndOfOptions returns a new EndOfOptions UDP extension. 202 | func NewEndOfOptions() Extension { return Extension{Type: EndOfOptions} } 203 | 204 | // NewNop returns a new Nop UDP extension. 205 | func NewNop() Extension { return Extension{Type: Nop} } 206 | 207 | // NewURLData returns a new URLData UDP extension. 208 | func NewURLData(data []byte) Extension { 209 | return Extension{Type: URLData, Length: uint8(len(data)), Data: data} 210 | } 211 | 212 | // ExtensionType represents the type of UDP extension. 213 | type ExtensionType uint8 214 | 215 | func (et ExtensionType) String() string { 216 | switch et { 217 | case EndOfOptions: 218 | return "EndOfOptions" 219 | case Nop: 220 | return "Nop" 221 | case URLData: 222 | return "URLData" 223 | default: 224 | return fmt.Sprintf("ExtensionType(%d)", et) 225 | } 226 | } 227 | 228 | // Extension represent the extension used by the UDP ANNOUNCE request. 229 | // 230 | // BEP 41 231 | type Extension struct { 232 | Type ExtensionType 233 | Length uint8 234 | Data []byte 235 | } 236 | 237 | // EncodeTo encodes the response to buf. 238 | func (e Extension) EncodeTo(buf *bytes.Buffer) { 239 | if _len := uint8(len(e.Data)); e.Length == 0 && _len != 0 { 240 | e.Length = _len 241 | } else if _len != e.Length { 242 | panic("the length of data is inconsistent") 243 | } 244 | 245 | buf.WriteByte(byte(e.Type)) 246 | buf.WriteByte(e.Length) 247 | buf.Write(e.Data) 248 | } 249 | 250 | // DecodeFrom decodes the response from b. 251 | func (e *Extension) DecodeFrom(b []byte) (parsed int) { 252 | switch len(b) { 253 | case 0: 254 | case 1: 255 | e.Type = ExtensionType(b[0]) 256 | parsed = 1 257 | default: 258 | e.Type = ExtensionType(b[0]) 259 | e.Length = b[1] 260 | parsed = 2 261 | if e.Length > 0 { 262 | parsed += int(e.Length) 263 | e.Data = b[2:parsed] 264 | } 265 | } 266 | return 267 | } 268 | -------------------------------------------------------------------------------- /tracker/udptracker/udp_client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020~2023 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package udptracker 16 | 17 | import ( 18 | "bytes" 19 | "context" 20 | "encoding/binary" 21 | "errors" 22 | "fmt" 23 | "io" 24 | "net" 25 | "strings" 26 | "sync/atomic" 27 | "time" 28 | 29 | "github.com/xgfone/go-bt/metainfo" 30 | ) 31 | 32 | // NewClientByDial returns a new Client by dialing. 33 | func NewClientByDial(network, address string, id metainfo.Hash) (*Client, error) { 34 | conn, err := net.Dial(network, address) 35 | if err != nil { 36 | return nil, err 37 | } 38 | return NewClient(conn.(*net.UDPConn), id), nil 39 | } 40 | 41 | // NewClient returns a new Client. 42 | func NewClient(conn *net.UDPConn, id metainfo.Hash) *Client { 43 | ipv4 := strings.Contains(conn.LocalAddr().String(), ".") 44 | if id.IsZero() { 45 | id = metainfo.NewRandomHash() 46 | } 47 | 48 | return &Client{ 49 | MaxBufSize: maxBufSize, 50 | 51 | conn: conn, 52 | ipv4: ipv4, 53 | id: id, 54 | } 55 | } 56 | 57 | // Client is a tracker client based on UDP. 58 | // 59 | // Notice: the request is synchronized, that's, the last request is not returned, 60 | // the next request must not be sent. 61 | // 62 | // BEP 15 63 | type Client struct { 64 | MaxBufSize int // Default: 2048 65 | 66 | id metainfo.Hash 67 | conn *net.UDPConn 68 | last time.Time 69 | cid uint64 70 | tid uint32 71 | ipv4 bool 72 | } 73 | 74 | // Close closes the UDP tracker client. 75 | func (utc *Client) Close() error { return utc.conn.Close() } 76 | func (utc *Client) String() string { return utc.conn.RemoteAddr().String() } 77 | 78 | func (utc *Client) readResp(ctx context.Context, b []byte) (int, error) { 79 | done := make(chan struct{}) 80 | 81 | var n int 82 | var err error 83 | go func() { 84 | n, err = utc.conn.Read(b) 85 | close(done) 86 | }() 87 | 88 | select { 89 | case <-ctx.Done(): 90 | utc.conn.SetReadDeadline(time.Now()) 91 | return n, ctx.Err() 92 | case <-done: 93 | return n, err 94 | } 95 | } 96 | 97 | func (utc *Client) getTranID() uint32 { 98 | return atomic.AddUint32(&utc.tid, 1) 99 | } 100 | 101 | func (utc *Client) parseError(b []byte) (tid uint32, reason string) { 102 | tid = binary.BigEndian.Uint32(b[:4]) 103 | reason = string(b[4:]) 104 | return 105 | } 106 | 107 | func (utc *Client) send(b []byte) (err error) { 108 | n, err := utc.conn.Write(b) 109 | if err == nil && n < len(b) { 110 | err = io.ErrShortWrite 111 | } 112 | return 113 | } 114 | 115 | func (utc *Client) connect(ctx context.Context) (err error) { 116 | tid := utc.getTranID() 117 | buf := bytes.NewBuffer(make([]byte, 0, 16)) 118 | binary.Write(buf, binary.BigEndian, ProtocolID) 119 | binary.Write(buf, binary.BigEndian, ActionConnect) 120 | binary.Write(buf, binary.BigEndian, tid) 121 | if err = utc.send(buf.Bytes()); err != nil { 122 | return 123 | } 124 | 125 | data := make([]byte, 32) 126 | n, err := utc.readResp(ctx, data) 127 | if err != nil { 128 | return 129 | } else if n < 8 { 130 | return io.ErrShortBuffer 131 | } 132 | 133 | data = data[:n] 134 | switch action := binary.BigEndian.Uint32(data[:4]); action { 135 | case ActionConnect: 136 | case ActionError: 137 | _, reason := utc.parseError(data[4:]) 138 | return errors.New(reason) 139 | default: 140 | return fmt.Errorf("tracker response is not connect action: %d", action) 141 | } 142 | 143 | if n < 16 { 144 | return io.ErrShortBuffer 145 | } 146 | 147 | if _tid := binary.BigEndian.Uint32(data[4:8]); _tid != tid { 148 | return fmt.Errorf("expect transaction id %d, but got %d", tid, _tid) 149 | } 150 | 151 | utc.cid = binary.BigEndian.Uint64(data[8:16]) 152 | utc.last = time.Now() 153 | return 154 | } 155 | 156 | func (utc *Client) getConnectionID(ctx context.Context) (cid uint64, err error) { 157 | cid = utc.cid 158 | if time.Now().Sub(utc.last) > time.Minute { 159 | if err = utc.connect(ctx); err == nil { 160 | cid = utc.cid 161 | } 162 | } 163 | return 164 | } 165 | 166 | func (utc *Client) announce(ctx context.Context, req AnnounceRequest) (r AnnounceResponse, err error) { 167 | cid, err := utc.getConnectionID(ctx) 168 | if err != nil { 169 | return 170 | } 171 | 172 | tid := utc.getTranID() 173 | buf := bytes.NewBuffer(make([]byte, 0, 110)) 174 | binary.Write(buf, binary.BigEndian, cid) // 8: 0 - 8 175 | binary.Write(buf, binary.BigEndian, ActionAnnounce) // 4: 8 - 12 176 | binary.Write(buf, binary.BigEndian, tid) // 4: 12 - 16 177 | req.EncodeTo(buf) 178 | b := buf.Bytes() 179 | if err = utc.send(b); err != nil { 180 | return 181 | } 182 | 183 | bufSize := utc.MaxBufSize 184 | if bufSize <= 0 { 185 | bufSize = maxBufSize 186 | } 187 | 188 | data := make([]byte, bufSize) 189 | n, err := utc.readResp(ctx, data) 190 | if err != nil { 191 | return 192 | } else if n < 8 { 193 | err = io.ErrShortBuffer 194 | return 195 | } 196 | 197 | data = data[:n] 198 | switch action := binary.BigEndian.Uint32(data[:4]); action { 199 | case ActionAnnounce: 200 | case ActionError: 201 | _, reason := utc.parseError(data[4:]) 202 | err = errors.New(reason) 203 | return 204 | default: 205 | err = fmt.Errorf("tracker response is not connect action: %d", action) 206 | return 207 | } 208 | 209 | if n < 16 { 210 | err = io.ErrShortBuffer 211 | return 212 | } 213 | 214 | if _tid := binary.BigEndian.Uint32(data[4:8]); _tid != tid { 215 | err = fmt.Errorf("expect transaction id %d, but got %d", tid, _tid) 216 | return 217 | } 218 | 219 | r.DecodeFrom(data[8:], utc.ipv4) 220 | return 221 | } 222 | 223 | // Announce sends a Announce request to the tracker. 224 | // 225 | // Notice: 226 | // 1. if it does not connect to the UDP tracker server, it will connect to it, 227 | // then send the ANNOUNCE request. 228 | // 2. If returning an error, you should retry it. 229 | // See http://www.bittorrent.org/beps/bep_0015.html#time-outs 230 | func (utc *Client) Announce(c context.Context, r AnnounceRequest) (AnnounceResponse, error) { 231 | if r.InfoHash.IsZero() { 232 | panic("infohash is ZERO") 233 | } 234 | 235 | r.PeerID = utc.id 236 | return utc.announce(c, r) 237 | } 238 | 239 | func (utc *Client) scrape(c context.Context, ihs []metainfo.Hash) (rs []ScrapeResponse, err error) { 240 | cid, err := utc.getConnectionID(c) 241 | if err != nil { 242 | return 243 | } 244 | 245 | tid := utc.getTranID() 246 | buf := bytes.NewBuffer(make([]byte, 0, 16+len(ihs)*20)) 247 | binary.Write(buf, binary.BigEndian, cid) // 8: 0 - 8 248 | binary.Write(buf, binary.BigEndian, ActionScrape) // 4: 8 - 12 249 | binary.Write(buf, binary.BigEndian, tid) // 4: 12 - 16 250 | 251 | for _, h := range ihs { // 20*N: 16 - 252 | buf.Write(h[:]) 253 | } 254 | 255 | if err = utc.send(buf.Bytes()); err != nil { 256 | return 257 | } 258 | 259 | bufSize := utc.MaxBufSize 260 | if bufSize <= 0 { 261 | bufSize = maxBufSize 262 | } 263 | 264 | data := make([]byte, bufSize) 265 | n, err := utc.readResp(c, data) 266 | if err != nil { 267 | return 268 | } else if n < 8 { 269 | err = io.ErrShortBuffer 270 | return 271 | } 272 | 273 | data = data[:n] 274 | switch action := binary.BigEndian.Uint32(data[:4]); action { 275 | case ActionScrape: 276 | case ActionError: 277 | _, reason := utc.parseError(data[4:]) 278 | err = errors.New(reason) 279 | return 280 | default: 281 | err = fmt.Errorf("tracker response is not connect action %d", action) 282 | return 283 | } 284 | 285 | if _tid := binary.BigEndian.Uint32(data[4:8]); _tid != tid { 286 | err = fmt.Errorf("expect transaction id %d, but got %d", tid, _tid) 287 | return 288 | } 289 | 290 | data = data[8:] 291 | _len := len(data) 292 | rs = make([]ScrapeResponse, 0, _len/12) 293 | for i := 12; i <= _len; i += 12 { 294 | var r ScrapeResponse 295 | r.DecodeFrom(data[i-12 : i]) 296 | rs = append(rs, r) 297 | } 298 | 299 | return 300 | } 301 | 302 | // Scrape sends a Scrape request to the tracker. 303 | // 304 | // Notice: 305 | // 1. if it does not connect to the UDP tracker server, it will connect to it, 306 | // then send the ANNOUNCE request. 307 | // 2. If returning an error, you should retry it. 308 | // See http://www.bittorrent.org/beps/bep_0015.html#time-outs 309 | func (utc *Client) Scrape(c context.Context, hs []metainfo.Hash) ([]ScrapeResponse, error) { 310 | return utc.scrape(c, hs) 311 | } 312 | -------------------------------------------------------------------------------- /tracker/udptracker/udp_server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020~2023 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package udptracker 16 | 17 | import ( 18 | "bytes" 19 | "encoding/binary" 20 | "log" 21 | "net" 22 | "strings" 23 | "sync" 24 | "sync/atomic" 25 | "time" 26 | 27 | "github.com/xgfone/go-bt/metainfo" 28 | ) 29 | 30 | // ServerHandler is used to handle the request from the client. 31 | type ServerHandler interface { 32 | // OnConnect is used to check whether to make the connection or not. 33 | OnConnect(raddr *net.UDPAddr) (err error) 34 | OnAnnounce(raddr *net.UDPAddr, req AnnounceRequest) (AnnounceResponse, error) 35 | OnScrap(raddr *net.UDPAddr, infohashes []metainfo.Hash) ([]ScrapeResponse, error) 36 | } 37 | 38 | func encodeResponseHeader(buf *bytes.Buffer, action, tid uint32) { 39 | binary.Write(buf, binary.BigEndian, action) 40 | binary.Write(buf, binary.BigEndian, tid) 41 | } 42 | 43 | type wrappedPeerAddr struct { 44 | Addr *net.UDPAddr 45 | Time time.Time 46 | } 47 | 48 | type buffer struct{ Data []byte } 49 | 50 | // Server is a tracker server based on UDP. 51 | type Server struct { 52 | // Default: log.Printf 53 | ErrorLog func(format string, args ...interface{}) 54 | 55 | conn net.PacketConn 56 | handler ServerHandler 57 | bufpool sync.Pool 58 | 59 | cid uint64 60 | exit chan struct{} 61 | lock sync.RWMutex 62 | conns map[uint64]wrappedPeerAddr 63 | } 64 | 65 | // NewServer returns a new Server. 66 | func NewServer(c net.PacketConn, h ServerHandler, bufSize int) *Server { 67 | if bufSize <= 0 { 68 | bufSize = maxBufSize 69 | } 70 | 71 | return &Server{ 72 | conn: c, 73 | handler: h, 74 | exit: make(chan struct{}), 75 | conns: make(map[uint64]wrappedPeerAddr, 128), 76 | bufpool: sync.Pool{New: func() interface{} { 77 | return &buffer{Data: make([]byte, bufSize)} 78 | }}, 79 | } 80 | } 81 | 82 | // Close closes the tracker server. 83 | func (uts *Server) Close() { 84 | select { 85 | case <-uts.exit: 86 | default: 87 | close(uts.exit) 88 | uts.conn.Close() 89 | } 90 | } 91 | 92 | func (uts *Server) cleanConnectionID(interval time.Duration) { 93 | tick := time.NewTicker(interval) 94 | defer tick.Stop() 95 | for { 96 | select { 97 | case <-uts.exit: 98 | return 99 | case now := <-tick.C: 100 | uts.lock.RLock() 101 | for cid, wa := range uts.conns { 102 | if now.Sub(wa.Time) > interval { 103 | delete(uts.conns, cid) 104 | } 105 | } 106 | uts.lock.RUnlock() 107 | } 108 | } 109 | } 110 | 111 | // Run starts the tracker server. 112 | func (uts *Server) Run() { 113 | go uts.cleanConnectionID(time.Minute * 2) 114 | for { 115 | buf := uts.bufpool.Get().(*buffer) 116 | n, raddr, err := uts.conn.ReadFrom(buf.Data) 117 | if err != nil { 118 | if !strings.Contains(err.Error(), "closed") { 119 | uts.errorf("failed to read udp tracker request: %s", err) 120 | } 121 | return 122 | } else if n < 16 { 123 | continue 124 | } 125 | go uts.handleRequest(raddr.(*net.UDPAddr), buf, n) 126 | } 127 | } 128 | 129 | func (uts *Server) errorf(format string, args ...interface{}) { 130 | if uts.ErrorLog == nil { 131 | log.Printf(format, args...) 132 | } else { 133 | uts.ErrorLog(format, args...) 134 | } 135 | } 136 | 137 | func (uts *Server) handleRequest(raddr *net.UDPAddr, buf *buffer, n int) { 138 | defer uts.bufpool.Put(buf) 139 | uts.handlePacket(raddr, buf.Data[:n]) 140 | } 141 | 142 | func (uts *Server) send(raddr *net.UDPAddr, b []byte) { 143 | n, err := uts.conn.WriteTo(b, raddr) 144 | if err != nil { 145 | uts.errorf("fail to send the udp tracker response to '%s': %s", 146 | raddr.String(), err) 147 | } else if n < len(b) { 148 | uts.errorf("too short udp tracker response sent to '%s'", raddr.String()) 149 | } 150 | } 151 | 152 | func (uts *Server) getConnectionID() uint64 { 153 | return atomic.AddUint64(&uts.cid, 1) 154 | } 155 | 156 | func (uts *Server) addConnection(cid uint64, raddr *net.UDPAddr) { 157 | now := time.Now() 158 | uts.lock.Lock() 159 | uts.conns[cid] = wrappedPeerAddr{Addr: raddr, Time: now} 160 | uts.lock.Unlock() 161 | } 162 | 163 | func (uts *Server) checkConnection(cid uint64, raddr *net.UDPAddr) (ok bool) { 164 | uts.lock.RLock() 165 | if w, _ok := uts.conns[cid]; _ok && w.Addr.Port == raddr.Port && 166 | bytes.Equal(w.Addr.IP, raddr.IP) { 167 | ok = true 168 | } 169 | uts.lock.RUnlock() 170 | return 171 | } 172 | 173 | func (uts *Server) sendError(raddr *net.UDPAddr, tid uint32, reason string) { 174 | buf := bytes.NewBuffer(make([]byte, 0, 8+len(reason))) 175 | encodeResponseHeader(buf, ActionError, tid) 176 | buf.WriteString(reason) 177 | uts.send(raddr, buf.Bytes()) 178 | } 179 | 180 | func (uts *Server) sendConnResp(raddr *net.UDPAddr, tid uint32, cid uint64) { 181 | buf := bytes.NewBuffer(make([]byte, 0, 16)) 182 | encodeResponseHeader(buf, ActionConnect, tid) 183 | binary.Write(buf, binary.BigEndian, cid) 184 | uts.send(raddr, buf.Bytes()) 185 | } 186 | 187 | func (uts *Server) sendAnnounceResp(raddr *net.UDPAddr, tid uint32, resp AnnounceResponse) { 188 | buf := bytes.NewBuffer(make([]byte, 0, 8+12+len(resp.Addresses)*18)) 189 | encodeResponseHeader(buf, ActionAnnounce, tid) 190 | resp.EncodeTo(buf, raddr.IP.To4() != nil) 191 | uts.send(raddr, buf.Bytes()) 192 | } 193 | 194 | func (uts *Server) sendScrapResp(raddr *net.UDPAddr, tid uint32, rs []ScrapeResponse) { 195 | buf := bytes.NewBuffer(make([]byte, 0, 8+len(rs)*12)) 196 | encodeResponseHeader(buf, ActionScrape, tid) 197 | for _, r := range rs { 198 | r.EncodeTo(buf) 199 | } 200 | uts.send(raddr, buf.Bytes()) 201 | } 202 | 203 | func (uts *Server) handlePacket(raddr *net.UDPAddr, b []byte) { 204 | cid := binary.BigEndian.Uint64(b[:8]) // 8: 0 - 8 205 | action := binary.BigEndian.Uint32(b[8:12]) // 4: 8 - 12 206 | tid := binary.BigEndian.Uint32(b[12:16]) // 4: 12 - 16 207 | b = b[16:] 208 | 209 | // Handle the connection request. 210 | if cid == ProtocolID && action == ActionConnect { 211 | if err := uts.handler.OnConnect(raddr); err != nil { 212 | uts.sendError(raddr, tid, err.Error()) 213 | return 214 | } 215 | 216 | cid := uts.getConnectionID() 217 | uts.addConnection(cid, raddr) 218 | uts.sendConnResp(raddr, tid, cid) 219 | return 220 | } 221 | 222 | // Check whether the request is connected. 223 | if !uts.checkConnection(cid, raddr) { 224 | uts.sendError(raddr, tid, "connection is expired") 225 | return 226 | } 227 | 228 | switch action { 229 | case ActionAnnounce: 230 | var req AnnounceRequest 231 | if raddr.IP.To4() != nil { // For ipv4 232 | if len(b) < 82 { 233 | uts.sendError(raddr, tid, "invalid announce request") 234 | return 235 | } 236 | req.DecodeFrom(b) 237 | } else { // For ipv6 238 | if len(b) < 94 { 239 | uts.sendError(raddr, tid, "invalid announce request") 240 | return 241 | } 242 | req.DecodeFrom(b) 243 | } 244 | 245 | resp, err := uts.handler.OnAnnounce(raddr, req) 246 | if err != nil { 247 | uts.sendError(raddr, tid, err.Error()) 248 | } else { 249 | uts.sendAnnounceResp(raddr, tid, resp) 250 | } 251 | 252 | case ActionScrape: 253 | _len := len(b) 254 | infohashes := make([]metainfo.Hash, 0, _len/20) 255 | for i, _len := 20, len(b); i <= _len; i += 20 { 256 | infohashes = append(infohashes, metainfo.NewHash(b[i-20:i])) 257 | } 258 | 259 | if len(infohashes) == 0 { 260 | uts.sendError(raddr, tid, "no infohash") 261 | return 262 | } 263 | 264 | resps, err := uts.handler.OnScrap(raddr, infohashes) 265 | if err != nil { 266 | uts.sendError(raddr, tid, err.Error()) 267 | } else { 268 | uts.sendScrapResp(raddr, tid, resps) 269 | } 270 | 271 | default: 272 | uts.sendError(raddr, tid, "unkwnown action") 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /tracker/udptracker/udp_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020~2023 xgfone 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package udptracker 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | "fmt" 21 | "log" 22 | "net" 23 | "time" 24 | 25 | "github.com/xgfone/go-bt/metainfo" 26 | ) 27 | 28 | type testHandler struct{} 29 | 30 | func (testHandler) OnConnect(raddr *net.UDPAddr) (err error) { return } 31 | func (testHandler) OnAnnounce(raddr *net.UDPAddr, req AnnounceRequest) (r AnnounceResponse, err error) { 32 | if req.Port != 80 { 33 | err = errors.New("port is not 80") 34 | return 35 | } 36 | 37 | if len(req.Exts) > 0 { 38 | for i, ext := range req.Exts { 39 | switch ext.Type { 40 | case URLData: 41 | fmt.Printf("Extensions[%d]: URLData(%s)\n", i, string(ext.Data)) 42 | default: 43 | fmt.Printf("Extensions[%d]: %s\n", i, ext.Type.String()) 44 | } 45 | } 46 | } 47 | 48 | r = AnnounceResponse{ 49 | Interval: 1, 50 | Leechers: 2, 51 | Seeders: 3, 52 | Addresses: []metainfo.CompactAddr{{IP: net.ParseIP("127.0.0.1"), Port: 8001}}, 53 | } 54 | return 55 | } 56 | func (testHandler) OnScrap(raddr *net.UDPAddr, infohashes []metainfo.Hash) (rs []ScrapeResponse, err error) { 57 | rs = make([]ScrapeResponse, len(infohashes)) 58 | for i := range infohashes { 59 | rs[i] = ScrapeResponse{ 60 | Seeders: uint32(i)*10 + 1, 61 | Leechers: uint32(i)*10 + 2, 62 | Completed: uint32(i)*10 + 3, 63 | } 64 | } 65 | return 66 | } 67 | 68 | func ExampleClient() { 69 | // Start the UDP tracker server 70 | sconn, err := net.ListenPacket("udp4", "127.0.0.1:8001") 71 | if err != nil { 72 | log.Fatal(err) 73 | } 74 | server := NewServer(sconn, testHandler{}, 0) 75 | defer server.Close() 76 | go server.Run() 77 | 78 | // Wait for the server to be started 79 | time.Sleep(time.Second) 80 | 81 | // Create a client and dial to the UDP tracker server. 82 | client, err := NewClientByDial("udp4", "127.0.0.1:8001", metainfo.Hash{}) 83 | if err != nil { 84 | log.Fatal(err) 85 | } 86 | 87 | // Send the ANNOUNCE request to the UDP tracker server, 88 | // and get the ANNOUNCE response. 89 | exts := []Extension{NewURLData([]byte("data")), NewNop()} 90 | req := AnnounceRequest{InfoHash: metainfo.NewRandomHash(), Port: 80, Exts: exts} 91 | resp, err := client.Announce(context.Background(), req) 92 | if err != nil { 93 | log.Fatal(err) 94 | } 95 | 96 | fmt.Printf("Interval: %d\n", resp.Interval) 97 | fmt.Printf("Leechers: %d\n", resp.Leechers) 98 | fmt.Printf("Seeders: %d\n", resp.Seeders) 99 | for i, addr := range resp.Addresses { 100 | fmt.Printf("Address[%d].IP: %s\n", i, addr.IP.String()) 101 | fmt.Printf("Address[%d].Port: %d\n", i, addr.Port) 102 | } 103 | 104 | // Send the SCRAPE request to the UDP tracker server, 105 | // and get the SCRAPE respsone. 106 | hs := []metainfo.Hash{metainfo.NewRandomHash(), metainfo.NewRandomHash()} 107 | rs, err := client.Scrape(context.Background(), hs) 108 | if err != nil { 109 | log.Fatal(err) 110 | } else if len(rs) != 2 { 111 | log.Fatalf("%+v", rs) 112 | } 113 | 114 | for i, r := range rs { 115 | fmt.Printf("%d.Seeders: %d\n", i, r.Seeders) 116 | fmt.Printf("%d.Leechers: %d\n", i, r.Leechers) 117 | fmt.Printf("%d.Completed: %d\n", i, r.Completed) 118 | } 119 | 120 | // Unordered output: 121 | // Extensions[0]: URLData(data) 122 | // Extensions[1]: Nop 123 | // Interval: 1 124 | // Leechers: 2 125 | // Seeders: 3 126 | // Address[0].IP: 127.0.0.1 127 | // Address[0].Port: 8001 128 | // 0.Seeders: 1 129 | // 0.Leechers: 2 130 | // 0.Completed: 3 131 | // 1.Seeders: 11 132 | // 1.Leechers: 12 133 | // 1.Completed: 13 134 | } 135 | --------------------------------------------------------------------------------