1
// Copyright (c) 2016-2019 Uber Technologies, Inc.
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
7
// http://www.apache.org/licenses/LICENSE-2.0
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.
23
"github.com/jackpal/bencode-go"
26
// info contains the "instructions" for how to download / seed a torrent,
27
// primarily describing how a blob is broken up into pieces and how to verify
28
// those pieces (i.e. the piece sums).
30
// Exported for bencoding.
37
// Hash computes the InfoHash of info.
38
func (info *info) Hash() (InfoHash, error) {
40
if err := bencode.Marshal(&b, *info); err != nil {
41
return InfoHash{}, fmt.Errorf("bencode: %s", err)
43
return NewInfoHashFromBytes(b.Bytes()), nil
46
// MetaInfo contains torrent metadata.
53
// NewMetaInfo creates a new MetaInfo. Assumes that d is the valid digest for
54
// blob (re-computing it is expensive).
55
func NewMetaInfo(d Digest, blob io.Reader, pieceLength int64) (*MetaInfo, error) {
56
length, pieceSums, err := calcPieceSums(blob, pieceLength)
61
PieceLength: pieceLength,
68
return nil, fmt.Errorf("compute info hash: %s", err)
77
// InfoHash returns the torrent InfoHash.
78
func (mi *MetaInfo) InfoHash() InfoHash {
82
// Digest returns the digest of the original blob.
83
func (mi *MetaInfo) Digest() Digest {
87
// Length returns the length of the original blob.
88
func (mi *MetaInfo) Length() int64 {
92
// NumPieces returns the number of pieces in the torrent.
93
func (mi *MetaInfo) NumPieces() int {
94
return len(mi.info.PieceSums)
97
// PieceLength returns the piece length used to break up the original blob. Note,
98
// the final piece may be shorter than this. Use GetPieceLength for the true
99
// lengths of each piece.
100
func (mi *MetaInfo) PieceLength() int64 {
101
return mi.info.PieceLength
104
// GetPieceLength returns the length of piece i.
105
func (mi *MetaInfo) GetPieceLength(i int) int64 {
106
if i < 0 || i >= len(mi.info.PieceSums) {
109
if i == len(mi.info.PieceSums)-1 {
111
return mi.info.Length - mi.info.PieceLength*int64(i)
113
return mi.info.PieceLength
116
// GetPieceSum returns the checksum of piece i. Does not check bounds.
117
func (mi *MetaInfo) GetPieceSum(i int) uint32 {
118
return mi.info.PieceSums[i]
121
// metaInfoJSON is used for serializing / deserializing MetaInfo.
122
type metaInfoJSON struct {
123
// Only serialize info for backwards compatibility.
124
Info info `json:"Info"`
127
// Serialize converts mi to a json blob.
128
func (mi *MetaInfo) Serialize() ([]byte, error) {
129
return json.Marshal(&metaInfoJSON{mi.info})
132
// DeserializeMetaInfo reconstructs a MetaInfo from a json blob.
133
func DeserializeMetaInfo(data []byte) (*MetaInfo, error) {
135
if err := json.Unmarshal(data, &j); err != nil {
136
return nil, fmt.Errorf("json: %s", err)
138
h, err := j.Info.Hash()
140
return nil, fmt.Errorf("compute info hash: %s", err)
142
d, err := NewSHA256DigestFromHex(j.Info.Name)
144
return nil, fmt.Errorf("parse name: %s", err)
153
// calcPieceSums hashes blob content in pieceLength chunks.
154
func calcPieceSums(blob io.Reader, pieceLength int64) (length int64, pieceSums []uint32, err error) {
155
if pieceLength <= 0 {
156
return 0, nil, errors.New("piece length must be positive")
160
n, err := io.CopyN(h, blob, pieceLength)
161
if err != nil && err != io.EOF {
162
return 0, nil, fmt.Errorf("read blob: %s", err)
169
pieceSums = append(pieceSums, sum)
174
return length, pieceSums, nil