github.com/uber/kraken@v0.1.4/core/metainfo.go (about) 1 // Copyright (c) 2016-2019 Uber Technologies, Inc. 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 package core 15 16 import ( 17 "bytes" 18 "encoding/json" 19 "errors" 20 "fmt" 21 "io" 22 23 "github.com/jackpal/bencode-go" 24 ) 25 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). 29 type info struct { 30 // Exported for bencoding. 31 PieceLength int64 32 PieceSums []uint32 33 Name string 34 Length int64 35 } 36 37 // Hash computes the InfoHash of info. 38 func (info *info) Hash() (InfoHash, error) { 39 var b bytes.Buffer 40 if err := bencode.Marshal(&b, *info); err != nil { 41 return InfoHash{}, fmt.Errorf("bencode: %s", err) 42 } 43 return NewInfoHashFromBytes(b.Bytes()), nil 44 } 45 46 // MetaInfo contains torrent metadata. 47 type MetaInfo struct { 48 info info 49 infoHash InfoHash 50 digest Digest 51 } 52 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) 57 if err != nil { 58 return nil, err 59 } 60 info := info{ 61 PieceLength: pieceLength, 62 PieceSums: pieceSums, 63 Name: d.Hex(), 64 Length: length, 65 } 66 var buf bytes.Buffer 67 if err := bencode.Marshal(&buf, info); err != nil { 68 return nil, fmt.Errorf("bencode: %s", err) 69 } 70 h, err := info.Hash() 71 if err != nil { 72 return nil, fmt.Errorf("compute info hash: %s", err) 73 } 74 return &MetaInfo{ 75 info: info, 76 infoHash: h, 77 digest: d, 78 }, nil 79 } 80 81 // InfoHash returns the torrent InfoHash. 82 func (mi *MetaInfo) InfoHash() InfoHash { 83 return mi.infoHash 84 } 85 86 // Digest returns the digest of the original blob. 87 func (mi *MetaInfo) Digest() Digest { 88 return mi.digest 89 } 90 91 // Length returns the length of the original blob. 92 func (mi *MetaInfo) Length() int64 { 93 return mi.info.Length 94 } 95 96 // NumPieces returns the number of pieces in the torrent. 97 func (mi *MetaInfo) NumPieces() int { 98 return len(mi.info.PieceSums) 99 } 100 101 // PieceLength returns the piece length used to break up the original blob. Note, 102 // the final piece may be shorter than this. Use GetPieceLength for the true 103 // lengths of each piece. 104 func (mi *MetaInfo) PieceLength() int64 { 105 return mi.info.PieceLength 106 } 107 108 // GetPieceLength returns the length of piece i. 109 func (mi *MetaInfo) GetPieceLength(i int) int64 { 110 if i < 0 || i >= len(mi.info.PieceSums) { 111 return 0 112 } 113 if i == len(mi.info.PieceSums)-1 { 114 // Last piece. 115 return mi.info.Length - mi.info.PieceLength*int64(i) 116 } 117 return mi.info.PieceLength 118 } 119 120 // GetPieceSum returns the checksum of piece i. Does not check bounds. 121 func (mi *MetaInfo) GetPieceSum(i int) uint32 { 122 return mi.info.PieceSums[i] 123 } 124 125 // metaInfoJSON is used for serializing / deserializing MetaInfo. 126 type metaInfoJSON struct { 127 // Only serialize info for backwards compatibility. 128 Info info `json:"Info"` 129 } 130 131 // Serialize converts mi to a json blob. 132 func (mi *MetaInfo) Serialize() ([]byte, error) { 133 return json.Marshal(&metaInfoJSON{mi.info}) 134 } 135 136 // DeserializeMetaInfo reconstructs a MetaInfo from a json blob. 137 func DeserializeMetaInfo(data []byte) (*MetaInfo, error) { 138 var j metaInfoJSON 139 if err := json.Unmarshal(data, &j); err != nil { 140 return nil, fmt.Errorf("json: %s", err) 141 } 142 h, err := j.Info.Hash() 143 if err != nil { 144 return nil, fmt.Errorf("compute info hash: %s", err) 145 } 146 d, err := NewSHA256DigestFromHex(j.Info.Name) 147 if err != nil { 148 return nil, fmt.Errorf("parse name: %s", err) 149 } 150 return &MetaInfo{ 151 info: j.Info, 152 infoHash: h, 153 digest: d, 154 }, nil 155 } 156 157 // calcPieceSums hashes blob content in pieceLength chunks. 158 func calcPieceSums(blob io.Reader, pieceLength int64) (length int64, pieceSums []uint32, err error) { 159 if pieceLength <= 0 { 160 return 0, nil, errors.New("piece length must be positive") 161 } 162 for { 163 h := PieceHash() 164 n, err := io.CopyN(h, blob, pieceLength) 165 if err != nil && err != io.EOF { 166 return 0, nil, fmt.Errorf("read blob: %s", err) 167 } 168 length += n 169 if n == 0 { 170 break 171 } 172 sum := h.Sum32() 173 pieceSums = append(pieceSums, sum) 174 if n < pieceLength { 175 break 176 } 177 } 178 return length, pieceSums, nil 179 }