resolve merge conflict

This commit is contained in:
idk
2021-06-17 22:35:47 -04:00
12 changed files with 374 additions and 57 deletions

View File

@ -6,6 +6,7 @@ go:
- 1.12.x
- 1.13.x
- 1.14.x
- 1.15.x
env:
- GO111MODULE=on
script:

View File

@ -1,4 +1,4 @@
# BT - Another Implementation Based On Golang [![Build Status](https://travis-ci.org/xgfone/bt.svg?branch=master)](https://travis-ci.org/xgfone/bt) [![GoDoc](https://godoc.org/github.com/xgfone/bt?status.svg)](https://pkg.go.dev/github.com/xgfone/bt) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=flat-square)](https://raw.githubusercontent.com/xgfone/bt/master/LICENSE)
# BT - Another Implementation For Golang [![Build Status](https://api.travis-ci.com/xgfone/bt.svg?branch=master)](https://travis-ci.com/github/xgfone/bt) [![GoDoc](https://pkg.go.dev/badge/github.com/xgfone/bt)](https://pkg.go.dev/github.com/xgfone/bt) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=flat-square)](https://raw.githubusercontent.com/xgfone/bt/master/LICENSE)
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).

View File

@ -76,7 +76,7 @@ func newDHTServer(id metainfo.Hash, addr string, pm PeerManager) (s *Server, err
conn, err := net.ListenPacket("udp", addr)
if err == nil {
c := Config{ID: id, PeerManager: pm, OnSearch: onSearch, OnTorrent: onTorrent}
s = NewServer(conn, c)
s = NewServer(conn, conn, c)
}
return
}

View File

@ -84,7 +84,7 @@ func Host(raddr net.Addr) string {
func SplitHostPort(raddr net.Addr) (string, int) {
var host, port, err = net.SplitHostPort(raddr.String())
if err != nil {
host = net.ParseIP(raddr.String()).String()
//host = net.ParseIP(raddr.String()).String()
port = strings.Replace(raddr.String(), ":", "", -1)
}
if host == "" {
@ -275,8 +275,12 @@ func (a Address) MarshalBinary() (data []byte, err error) {
func (a *Address) decode(vs []interface{}) (err error) {
defer func() {
if e := recover(); e != nil {
err = e.(error)
switch e := recover().(type) {
case nil:
case error:
err = e
default:
err = fmt.Errorf("%v", e)
}
}()
@ -406,8 +410,12 @@ func (a HostAddress) Equal(o HostAddress) bool {
func (a *HostAddress) decode(vs []interface{}) (err error) {
defer func() {
if e := recover(); e != nil {
err = e.(error)
switch e := recover().(type) {
case nil:
case error:
err = e
default:
err = fmt.Errorf("%v", e)
}
}()

View File

@ -101,7 +101,7 @@ func (f File) FilePieces(info Info) (fps FilePieces) {
}}
}
fps = make(FilePieces, 0, endPieceIndex-startPieceIndex)
fps = make(FilePieces, 0, endPieceIndex-startPieceIndex+1)
fps = append(fps, FilePiece{
Index: uint32(startPieceIndex),
Offset: uint32(startPieceOffset),

View File

@ -52,36 +52,56 @@ type Info struct {
Files []File `json:"files,omitempty" bencode:"files,omitempty"` // BEP 3
}
// NewInfoFromFilePath returns a new Info from a file or directory.
func NewInfoFromFilePath(root string, pieceLength int64) (info Info, err error) {
info.Name = filepath.Base(root)
info.PieceLength = pieceLength
err = filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
if err != nil {
// getAllInfoFiles returns the list of the all files in a certain directory
// recursively.
func getAllInfoFiles(rootDir string) (files []File, err error) {
err = filepath.Walk(rootDir, func(path string, fi os.FileInfo, err error) error {
if err != nil || fi.IsDir() {
return err
}
if path == root && !fi.IsDir() { // The root is a file.
info.Length = fi.Size()
return nil
}
relPath, err := filepath.Rel(root, path)
relPath, err := filepath.Rel(rootDir, path)
if err != nil {
return fmt.Errorf("error getting relative path: %s", err)
}
info.Files = append(info.Files, File{
Paths: strings.Split(relPath, string(filepath.Separator)),
paths := strings.Split(relPath, string(filepath.Separator))
for _, name := range paths {
if name == ".git" {
return nil
}
}
files = append(files, File{
Paths: paths,
Length: fi.Size(),
})
return nil
})
if err == nil {
if err == nil && len(files) == 0 {
err = fmt.Errorf("no files in the directory '%s'", rootDir)
}
return
}
// NewInfoFromFilePath returns a new Info from a file or directory.
func NewInfoFromFilePath(root string, pieceLength int64) (info Info, err error) {
root = filepath.Clean(root)
fi, err := os.Stat(root)
if err != nil {
return
} else if !fi.IsDir() {
info.Length = fi.Size()
} else if info.Files, err = getAllInfoFiles(root); err != nil {
return
}
sort.Sort(files(info.Files))
info.Pieces, err = GeneratePiecesFromFiles(info.AllFiles(), info.PieceLength,
info.Pieces, err = GeneratePiecesFromFiles(info.AllFiles(), pieceLength,
func(file File) (io.ReadCloser, error) {
if _len := len(file.Paths); _len > 0 {
paths := make([]string, 0, _len+1)
@ -92,10 +112,12 @@ func NewInfoFromFilePath(root string, pieceLength int64) (info Info, err error)
return os.Open(root)
})
if err != nil {
if err == nil {
info.Name = filepath.Base(root)
info.PieceLength = pieceLength
} else {
err = fmt.Errorf("error generating pieces: %s", err)
}
}
return
}
@ -127,19 +149,33 @@ func (info Info) PieceOffset(index, offset uint32) int64 {
}
// GetFileByOffset returns the file and its offset by the total offset.
//
// If fileOffset is eqaul to file.Length, it means to reach the end.
func (info Info) GetFileByOffset(offset int64) (file File, fileOffset int64) {
if !info.IsDir() {
return File{Length: info.Length}, offset
if offset > info.Length {
panic(fmt.Errorf("offset '%d' exceeds the maximum length '%d'",
offset, info.Length))
}
return File{Length: info.Length, Paths: []string{info.Name}}, offset
}
fileOffset = offset
for _, file = range info.Files {
for i, _len := 0, len(info.Files)-1; i <= _len; i++ {
file = info.Files[i]
if fileOffset < file.Length {
return
} else if fileOffset == file.Length && i == _len {
return
}
fileOffset -= file.Length
}
if fileOffset > file.Length {
panic(fmt.Errorf("offset '%d' exceeds the maximum length '%d'",
offset, info.TotalLength()))
}
return
}

98
metainfo/info_test.go Normal file
View File

@ -0,0 +1,98 @@
// Copyright 2020 xgfone
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package metainfo
import "testing"
func TestInfo_GetFileByOffset(t *testing.T) {
FileInfo := Info{
Name: "test_rw",
PieceLength: 64,
Length: 600,
}
file, fileOffset := FileInfo.GetFileByOffset(0)
if file.Offset(FileInfo) != 0 || fileOffset != 0 {
t.Errorf("expect fileOffset='%d', but got '%d'", 0, fileOffset)
}
file, fileOffset = FileInfo.GetFileByOffset(100)
if file.Offset(FileInfo) != 0 || fileOffset != 100 {
t.Errorf("expect fileOffset='%d', but got '%d'", 100, fileOffset)
}
file, fileOffset = FileInfo.GetFileByOffset(600)
if file.Offset(FileInfo) != 0 || fileOffset != 600 {
t.Errorf("expect fileOffset='%d', but got '%d'", 600, fileOffset)
}
DirInfo := Info{
Name: "test_rw",
PieceLength: 64,
Files: []File{
{Length: 100, Paths: []string{"file1"}},
{Length: 200, Paths: []string{"file2"}},
{Length: 300, Paths: []string{"file3"}},
},
}
file, fileOffset = DirInfo.GetFileByOffset(0)
if file.Offset(DirInfo) != 0 || file.Length != 100 || fileOffset != 0 {
t.Errorf("expect fileOffset='%d', but got '%d'", 0, fileOffset)
}
file, fileOffset = DirInfo.GetFileByOffset(50)
if file.Offset(DirInfo) != 0 || file.Length != 100 || fileOffset != 50 {
t.Errorf("expect fileOffset='%d', but got '%d'", 50, fileOffset)
}
file, fileOffset = DirInfo.GetFileByOffset(100)
if file.Offset(DirInfo) != 100 || file.Length != 200 || fileOffset != 0 {
t.Errorf("expect fileOffset='%d', but got '%d'", 0, fileOffset)
}
file, fileOffset = DirInfo.GetFileByOffset(200)
if file.Offset(DirInfo) != 100 || file.Length != 200 || fileOffset != 100 {
t.Errorf("expect fileOffset='%d', but got '%d'", 100, fileOffset)
}
file, fileOffset = DirInfo.GetFileByOffset(300)
if file.Offset(DirInfo) != 300 || file.Length != 300 || fileOffset != 0 {
t.Errorf("expect fileOffset='%d', but got '%d'", 0, fileOffset)
}
file, fileOffset = DirInfo.GetFileByOffset(400)
if file.Offset(DirInfo) != 300 || file.Length != 300 || fileOffset != 100 {
t.Errorf("expect fileOffset='%d', but got '%d'", 100, fileOffset)
}
file, fileOffset = DirInfo.GetFileByOffset(600)
if file.Offset(DirInfo) != 300 || file.Length != 300 || fileOffset != 300 {
t.Errorf("expect fileOffset='%d', but got '%d'", 300, fileOffset)
}
}
func TestNewInfoFromFilePath(t *testing.T) {
info, err := NewInfoFromFilePath("info.go", PieceSize256KB)
if err != nil {
t.Error(err)
} else if info.Name != "info.go" || info.Files != nil {
t.Errorf("invalid info %+v\n", info)
}
info, err = NewInfoFromFilePath("../metainfo", PieceSize256KB)
if err != nil {
t.Error(err)
} else if info.Name != "metainfo" || info.Files == nil || info.Length > 0 {
t.Errorf("invalid info %+v\n", info)
}
info, err = NewInfoFromFilePath("../../bt", PieceSize256KB)
if err != nil {
t.Error(err)
} else if info.Name != "bt" || info.Files == nil || info.Length > 0 {
t.Errorf("invalid info %+v\n", info)
}
}

View File

@ -20,7 +20,9 @@ import (
"crypto/sha1"
"encoding/base32"
"encoding/hex"
"errors"
"fmt"
"io"
"github.com/xgfone/bt/bencode"
)
@ -96,6 +98,26 @@ func (h Hash) IsZero() bool {
return h == zeroHash
}
// WriteBinary is the same as MarshalBinary, but writes the result into w
// instead of returning.
func (h Hash) WriteBinary(w io.Writer) (m int, err error) {
return w.Write(h[:])
}
// UnmarshalBinary implements the interface binary.BinaryUnmarshaler.
func (h *Hash) UnmarshalBinary(b []byte) (err error) {
if len(b) < HashSize {
return errors.New("Hash.UnmarshalBinary: too few bytes")
}
copy((*h)[:], b[:HashSize])
return
}
// MarshalBinary implements the interface binary.BinaryMarshaler.
func (h Hash) MarshalBinary() (data []byte, err error) {
return h[:], nil
}
// MarshalBencode implements the interface bencode.Marshaler.
func (h Hash) MarshalBencode() (b []byte, err error) {
return bencode.EncodeBytes(h[:])

80
metainfo/infohash_test.go Normal file
View File

@ -0,0 +1,80 @@
// Copyright 2020 xgfone
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package metainfo
import "testing"
func TestHash(t *testing.T) {
hexHash := "0001020304050607080909080706050403020100"
b, err := NewHashFromHexString(hexHash).MarshalBencode()
if err != nil {
t.Fatal(err)
}
var h Hash
if err = h.UnmarshalBencode(b); err != nil {
t.Fatal(err)
}
if hexs := h.String(); hexs != hexHash {
t.Errorf("expect '%s', but got '%s'\n", hexHash, hexs)
}
h = Hash{}
hexHash = "0001020304050607080900010203040506070809"
err = h.UnmarshalBinary([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9})
if err != nil {
t.Error(err)
} else if hexs := h.HexString(); hexs != hexHash {
t.Errorf("expect '%s', but got '%s'\n", hexHash, hexs)
}
}
func TestHashes(t *testing.T) {
hexHash1 := "0101010101010101010101010101010101010101"
hexHash2 := "0202020202020202020202020202020202020202"
hashes := Hashes{
NewHashFromHexString(hexHash1),
NewHashFromHexString(hexHash2),
}
b, err := hashes.MarshalBencode()
if err != nil {
t.Fatal(err)
}
hashes = Hashes{}
if err = hashes.UnmarshalBencode(b); err != nil {
t.Fatal(err)
}
if _len := len(hashes); _len != 2 {
t.Fatalf("expect the len(hashes)==2, but got '%d'", _len)
}
for i, h := range hashes {
if i == 0 {
if hexs := h.HexString(); hexs != hexHash1 {
t.Errorf("index %d: expect '%s', but got '%s'\n", i, hexHash1, hexs)
}
} else {
if hexs := h.HexString(); hexs != hexHash2 {
t.Errorf("index %d: expect '%s', but got '%s'\n", i, hexHash2, hexs)
}
}
}
}

View File

@ -69,16 +69,14 @@ func (r *reader) ReadAt(p []byte, offset int64) (n int, err error) {
for _len := len(p); n < _len; {
file, fileOffset := r.info.GetFileByOffset(offset)
if file.Length == 0 {
if file.Length == 0 || file.Length == fileOffset {
err = io.EOF
break
}
length := int(file.Length-fileOffset) + n
if _len < length {
length = _len
} else if length <= n {
err = io.EOF
break
pend := n + int(file.Length-fileOffset)
if pend > _len {
pend = _len
}
filename := file.PathWithPrefix(r.root, r.info)
@ -86,7 +84,7 @@ func (r *reader) ReadAt(p []byte, offset int64) (n int, err error) {
break
}
m, err = f.ReadAt(p[n:length], fileOffset)
m, err = f.ReadAt(p[n:pend], fileOffset)
n += m
offset += int64(m)
if err != nil {

View File

@ -0,0 +1,78 @@
// Copyright 2020 xgfone
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package metainfo
import (
"bytes"
"os"
"testing"
)
func generateTestPieceData(len int, numData byte) []byte {
b := make([]byte, len)
for i := 0; i < len; i++ {
b[i] = numData
}
return b
}
func TestWriterAndReader(t *testing.T) {
info := Info{
Name: "test_rw",
PieceLength: 64,
Files: []File{
{Length: 100, Paths: []string{"file1"}},
{Length: 200, Paths: []string{"file2"}},
{Length: 300, Paths: []string{"file3"}},
},
}
r := NewReader("", info)
w := NewWriter("", info, 0600)
defer func() {
w.Close()
r.Close()
os.RemoveAll("test_rw")
}()
datalen := 600
wdata := make([]byte, datalen)
for i := 0; i < datalen; i++ {
wdata[i] = byte(i%26 + 97)
}
n, err := w.WriteAt(wdata, 0)
if err != nil {
t.Error(err)
return
} else if n != len(wdata) {
t.Errorf("expect wrote '%d', but got '%d'\n", len(wdata), n)
return
}
rdata := make([]byte, datalen)
n, err = r.ReadAt(rdata, 0)
if err != nil {
t.Error(err)
return
} else if n != len(rdata) {
t.Errorf("expect read '%d', but got '%d'\n", len(rdata), n)
return
}
if bytes.Compare(rdata, wdata) != 0 {
t.Errorf("expect read '%x', but got '%x'\n", wdata, rdata)
}
}

View File

@ -40,12 +40,12 @@ type writer struct {
// NewWriter returns a new Writer.
//
// If fileMode is equal to 0, it is 0700 by default.
// If fileMode is equal to 0, it is 0600 by default.
//
// Notice: fileMode is only used when writing the data.
func NewWriter(rootDir string, info Info, fileMode os.FileMode) Writer {
if fileMode == 0 {
fileMode = 0700
fileMode = 0600
}
return &writer{
@ -63,13 +63,8 @@ func (w *writer) open(filename string) (f *os.File, err error) {
f, ok := w.files[filename]
if !ok {
mode := w.mode
if mode == 0 {
mode = 0700
}
if err = os.MkdirAll(filepath.Dir(filename), mode); err == nil {
if f, err = os.OpenFile(filename, wflag, 0700); err == nil {
if err = os.MkdirAll(filepath.Dir(filename), 0700); err == nil {
if f, err = os.OpenFile(filename, wflag, w.mode); err == nil {
w.files[filename] = f
}
}
@ -103,7 +98,8 @@ func (w *writer) WriteAt(p []byte, offset int64) (n int, err error) {
for _len := len(p); n < _len; {
file, fileOffset := w.info.GetFileByOffset(offset)
if file.Length == 0 {
if file.Length == 0 || file.Length == fileOffset {
err = io.ErrShortWrite
break
}