Skip to content

Commit

Permalink
implement bitcomet padding files
Browse files Browse the repository at this point in the history
  • Loading branch information
cenkalti committed Oct 23, 2022
1 parent 4f472ff commit c7bf360
Show file tree
Hide file tree
Showing 14 changed files with 419 additions and 202 deletions.
21 changes: 13 additions & 8 deletions internal/allocator/allocator.go
Expand Up @@ -20,6 +20,7 @@ type Allocator struct {
type File struct {
Storage storage.File
Name string
Padding bool
}

// Progress about the allocation.
Expand Down Expand Up @@ -64,16 +65,20 @@ func (a *Allocator) Run(info *metainfo.Info, sto storage.Storage, progressC chan
for i, f := range info.Files {
var sf storage.File
var exists bool
sf, exists, a.Error = sto.Open(f.Path, f.Length)
if a.Error != nil {
return
}
a.Files[i] = File{Storage: sf, Name: f.Path}
if exists {
a.HasExisting = true
if f.Padding {
sf = storage.NewPaddingFile(f.Length)
} else {
a.HasMissing = true
sf, exists, a.Error = sto.Open(f.Path, f.Length)
if a.Error != nil {
return
}
if exists {
a.HasExisting = true
} else {
a.HasMissing = true
}
}
a.Files[i] = File{Storage: sf, Name: f.Path, Padding: f.Padding}
allocatedSize += f.Length
a.sendProgress(progressC, allocatedSize)
}
Expand Down
7 changes: 6 additions & 1 deletion internal/bufferpool/bufferpool.go
Expand Up @@ -35,11 +35,16 @@ type Buffer struct {
}

func newBuffer(buf *[]byte, length int, pool *Pool) Buffer {
return Buffer{
b := Buffer{
Data: (*buf)[:length],
buf: buf,
pool: pool,
}
// Clear contents before returning to the caller.
for i := range b.Data {
b.Data[i] = 0
}
return b
}

// Release the Buffer and return it to the Pool.
Expand Down
3 changes: 3 additions & 0 deletions internal/filesection/section.go
Expand Up @@ -8,6 +8,9 @@ type FileSection struct {
Offset int64
Length int64
Name string
// Padding indicates that the file is used as padding so it should not be requested from peers.
// The contents of padding files are always zero.
Padding bool
}

// ReadWriterAt combines the io.ReaderAt and io.WriterAt interfaces.
Expand Down
8 changes: 4 additions & 4 deletions internal/filesection/section_test.go
Expand Up @@ -32,10 +32,10 @@ func TestFiles(t *testing.T) {
}
}
files := []FileSection{
{osFiles[0], 2, 2, ""},
{osFiles[1], 0, 1, ""},
{osFiles[2], 0, 0, ""},
{osFiles[3], 0, 2, ""},
{osFiles[0], 2, 2, "", false},
{osFiles[1], 0, 1, "", false},
{osFiles[2], 0, 0, "", false},
{osFiles[3], 0, 2, "", false},
}
pf := Piece(files)

Expand Down
62 changes: 43 additions & 19 deletions internal/metainfo/info.go
Expand Up @@ -40,25 +40,53 @@ type Info struct {
type File struct {
Length int64
Path string
// https://www.bittorrent.org/beps/bep_0047.html
Padding bool
}

type file struct {
Length int64 `bencode:"length"`
Path []string `bencode:"path"`
PathUTF8 []string `bencode:"path.utf-8,omitempty"`
Attr string `bencode:"attr"`
}

// NewInfo returns info from bencoded bytes in b.
func NewInfo(b []byte, utf8 bool) (*Info, error) {
var ib struct {
PieceLength uint32 `bencode:"piece length"`
Pieces []byte `bencode:"pieces"`
Name string `bencode:"name"`
NameUTF8 string `bencode:"name.utf-8,omitempty"`
Private bencode.RawMessage `bencode:"private"`
Length int64 `bencode:"length"` // Single File Mode
Files []file `bencode:"files"` // Multiple File mode
func (f *file) isPadding() bool {
// BEP 0047
if strings.ContainsRune(f.Attr, 'p') {
return true
}
// BitComet convention that do not conform BEP 0047
if len(f.Path) > 0 && strings.HasPrefix(f.Path[len(f.Path)-1], "_____padding_file") {
return true
}
return false
}

type infoType struct {
PieceLength uint32 `bencode:"piece length"`
Pieces []byte `bencode:"pieces"`
Name string `bencode:"name"`
NameUTF8 string `bencode:"name.utf-8,omitempty"`
Private bencode.RawMessage `bencode:"private"`
Length int64 `bencode:"length"` // Single File Mode
Files []file `bencode:"files"` // Multiple File mode
}

func (ib *infoType) overrideUTF8Keys() {
if len(ib.NameUTF8) > 0 {
ib.Name = ib.NameUTF8
}
for i := range ib.Files {
if len(ib.Files[i].PathUTF8) > 0 {
ib.Files[i].Path = ib.Files[i].PathUTF8
}
}
}

// NewInfo returns info from bencoded bytes in b.
func NewInfo(b []byte, utf8 bool, pad bool) (*Info, error) {
var ib infoType
if err := bencode.DecodeBytes(b, &ib); err != nil {
return nil, err
}
Expand All @@ -72,15 +100,8 @@ func NewInfo(b []byte, utf8 bool) (*Info, error) {
if numPieces == 0 {
return nil, errZeroPieces
}
if utf8 { // override name and path from utf8 keys
if len(ib.NameUTF8) > 0 {
ib.Name = ib.NameUTF8
}
for i := range ib.Files {
if len(ib.Files[i].PathUTF8) > 0 {
ib.Files[i].Path = ib.Files[i].PathUTF8
}
}
if utf8 {
ib.overrideUTF8Keys()
}
// ".." is not allowed in file names
for _, file := range ib.Files {
Expand Down Expand Up @@ -137,6 +158,9 @@ func NewInfo(b []byte, utf8 bool) (*Info, error) {
Path: filepath.Join(parts...),
Length: f.Length,
}
if pad {
i.Files[j].Padding = f.isPadding()
}
}
} else {
i.Files = []File{{Path: cleanName(i.Name), Length: i.Length}}
Expand Down
2 changes: 1 addition & 1 deletion internal/metainfo/metainfo.go
Expand Up @@ -36,7 +36,7 @@ func New(r io.Reader) (*MetaInfo, error) {
if len(t.Info) == 0 {
return nil, errors.New("no info dict in torrent file")
}
info, err := NewInfo(t.Info, true)
info, err := NewInfo(t.Info, true, true)
if err != nil {
return nil, err
}
Expand Down
103 changes: 61 additions & 42 deletions internal/piece/piece.go
Expand Up @@ -25,9 +25,8 @@ type Piece struct {

// Block is part of a Piece that is specified in peerprotocol.Request messages.
type Block struct {
Index int // index in piece
Begin uint32 // offset in piece
Length uint32 // always equal to BlockSize except the last block of a piece.
Begin uint32 // Offset in piece
Length uint32 // Cannot exceed BlockSize. It's shorter for last block or if the next file is a padding file.
}

// NewPieces returns a slice of Pieces by mapping files to the pieces.
Expand Down Expand Up @@ -70,10 +69,11 @@ func NewPieces(info *metainfo.Info, files []allocator.File) []Piece {
n := uint32(min(int64(left), fileLeft())) // number of bytes to write

file := filesection.FileSection{
File: files[fileIndex].Storage,
Offset: fileOffset,
Length: int64(n),
Name: files[fileIndex].Name,
File: files[fileIndex].Storage,
Offset: fileOffset,
Length: int64(n),
Name: files[fileIndex].Name,
Padding: files[fileIndex].Padding,
}
sections = append(sections, file)

Expand All @@ -97,8 +97,10 @@ func NewPieces(info *metainfo.Info, files []allocator.File) []Piece {
return pieces
}

// NumBlocks returns the number of blocks in the piece.
func (p *Piece) NumBlocks() int {
// numBlocks returns the number of blocks in the piece.
// The calculation is only correct when there is no padding in piece.
// It is only used in per-allocation of blocks slice in CalculateBlocks().
func (p *Piece) numBlocks() int {
div, mod := divmod(p.Length, BlockSize)
numBlocks := div
if mod != 0 {
Expand All @@ -107,44 +109,61 @@ func (p *Piece) NumBlocks() int {
return int(numBlocks)
}

// GetBlock returns the Block at index i.
func (p *Piece) GetBlock(i int) (b Block, ok bool) {
div, mod := divmod(p.Length, BlockSize)
numBlocks := int(div)
if mod != 0 {
numBlocks++
}
if i >= numBlocks {
return
}
var blen uint32
if mod != 0 && i == numBlocks-1 {
blen = mod
} else {
blen = BlockSize
}
return Block{
Index: i,
Begin: uint32(i) * BlockSize,
Length: blen,
}, true
func (p *Piece) CalculateBlocks() []Block {
return p.calculateBlocks(BlockSize)
}

// FindBlock returns the block at offset `begin` and length `length`.
func (p *Piece) FindBlock(begin, length uint32) (b Block, ok bool) {
idx, mod := divmod(begin, BlockSize)
if mod != 0 {
return
func (p *Piece) calculateBlocks(blockSize uint32) []Block {
blocks := make([]Block, 0, p.numBlocks())

secIndex := 0
sec := p.Data[secIndex]
blk := Block{
Begin: 0,
Length: 0,
}
pieceOffset := uint32(0)
secOffset := uint32(0)
blkLeft := func() uint32 { return blockSize - blk.Length }
secLeft := func() uint32 { return uint32(sec.Length) - secOffset }
nextBlock := func() {
if blk.Length == 0 {
return
}
blocks = append(blocks, blk)
blk.Begin = pieceOffset
blk.Length = 0
}
b, ok = p.GetBlock(int(idx))
if !ok {
return
hasNextSection := true
nextSection := func() {
secIndex++
if secIndex == len(p.Data) {
hasNextSection = false
return
}
secOffset = 0
sec = p.Data[secIndex]
}
if b.Length != length {
ok = false
return
for hasNextSection {
if sec.Padding {
pieceOffset += uint32(sec.Length)
nextBlock()
nextSection()
continue
}
n := min(secLeft(), blkLeft())
blk.Length += n
pieceOffset += n
secOffset += n
if blkLeft() == 0 {
nextBlock()
}
if secLeft() == 0 {
nextSection()
}
}
return b, true
nextBlock()
return blocks
}

// VerifyHash returns true if hash of piece data in buffer `buf` matches the hash of Piece.
Expand Down

0 comments on commit c7bf360

Please sign in to comment.