-
Notifications
You must be signed in to change notification settings - Fork 0
/
metainfo.go
87 lines (72 loc) · 2.23 KB
/
metainfo.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
package decoder
import (
"crypto/sha1"
"io"
"log/slog"
"strings"
"github.com/WendelHime/gotorrent/internal/shared/models"
"github.com/zeebo/bencode"
)
type MetafileDecoder interface {
Decode(io.Reader) (models.Metafile, error)
}
type decoder struct{}
func NewDecoder() MetafileDecoder {
return decoder{}
}
// serialization struct the represents the structure of a .torrent file
// it is not immediately usable, so it can be converted to a TorrentFile struct
type bencodeTorrent struct {
// URL of tracker server to get peers from
Announce string `bencode:"announce"`
AnnounceList [][]string `bencode:"announce-list"`
// Info is parsed as a RawMessage to ensure that the final info_hash is
// correct even in the case of the info dictionary being an unexpected shape
Info bencode.RawMessage `bencode:"info"`
}
func (decoder) Decode(torrent io.Reader) (models.Metafile, error) {
var response models.Metafile
var bt bencodeTorrent
err := bencode.NewDecoder(torrent).Decode(&bt)
if err != nil {
slog.Error("failed to decode torrent: %v", err)
return response, err
}
infoHash := calculateInfoHash(bt.Info)
response.Announce = bt.Announce
response.AnnounceList = bt.AnnounceList
response.InfoHash = models.Hash{Hash: infoHash}
err = bencode.NewDecoder(strings.NewReader(string(bt.Info))).Decode(&response.Info)
if err != nil {
slog.Error("failed to decode torrent info: %v", err)
return response, err
}
response.Info.PiecesHashes, err = calculatePiecesHashes(response.Info.Pieces)
if err != nil {
slog.Error("failed to calculate pieces hashes: %v", err)
return response, err
}
if response.Info.Length > 0 {
response.Info.Files = []models.File{{Length: response.Info.Length, Path: []string{response.Info.Name}}}
}
return response, nil
}
func calculateInfoHash(info []byte) [20]byte {
return sha1.Sum(info)
}
func calculatePiecesHashes(pieces string) ([]models.Hash, error) {
piecesHashes := make([]models.Hash, 0)
reader := strings.NewReader(pieces)
for {
hash := make([]byte, 20)
_, err := reader.Read(hash)
if err != nil && err != io.EOF {
return []models.Hash{}, err
}
if err == io.EOF {
break
}
piecesHashes = append(piecesHashes, models.Hash{Hash: [20]byte(hash)})
}
return piecesHashes, nil
}