-
Notifications
You must be signed in to change notification settings - Fork 0
/
hash.go
134 lines (116 loc) · 2.59 KB
/
hash.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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package mktorrent
import (
"crypto/sha1"
"fmt"
"io"
"sync"
)
type piece struct {
data []byte
hash []byte
}
func hashRoutine(pieces <-chan piece, wg *sync.WaitGroup, pro Progress) {
for piece := range pieces {
hash := sha1.Sum(piece.data)
copy(piece.hash, hash[:])
pro.Increment()
}
wg.Done()
}
// Digest needs to be constructed by NewHash.
type Digest struct {
intra []byte
piece []byte
piecesRead int64
pieceCount int64
pieceSize int64
pieces chan piece
wg *sync.WaitGroup
complete string
hash []byte
offset int
closed bool
}
// String returns the hexadecimal encoding of the hash if complete. If not it returns "".
func (d *Digest) String() string {
return d.complete
}
// Complete completes the hash first by appending to it the last irregular piece.
// Complete then returns the complete hash or error if any.
func (d *Digest) Complete() ([]byte, error) {
if len(d.intra) != len(d.piece) {
remaining := len(d.piece) - len(d.intra)
d.piece = d.piece[:remaining]
err := d.push()
if err != nil {
return nil, err
}
}
close(d.pieces)
d.closed = true
d.wg.Wait() // wait for the hash to complete
d.complete = fmt.Sprintf("%x", d.hash)
return d.hash, nil
}
func NewHash(pieceSize, pieceCount int64, goroutines int, pro Progress) *Digest {
if goroutines < 1 {
goroutines = 1
}
buf := make([]byte, pieceSize)
d := &Digest{
piece: buf,
intra: buf,
pieceSize: pieceSize,
pieceCount: pieceCount,
pieces: make(chan piece, goroutines*2),
wg: &sync.WaitGroup{},
hash: make([]byte, pieceCount*sha1.Size),
}
for i := 0; i < goroutines; i++ {
d.wg.Add(1)
go hashRoutine(d.pieces, d.wg, pro)
}
return d
}
func (d *Digest) push() error {
if d.piecesRead == d.pieceCount {
return io.ErrShortBuffer
}
end := d.offset + sha1.Size
d.pieces <- piece{data: d.piece, hash: d.hash[d.offset:end]}
d.offset = end
d.piecesRead++
return nil
}
// ReadFrom hashes data from r until EOF or error.
//
// It returns the number of bytes read.
// Any error except io.EOF encountered during the read is also returned.
func (d *Digest) ReadFrom(r io.Reader) (int64, error) {
var nt int64
for {
n, err := io.ReadFull(r, d.intra)
nt += int64(n)
if err != nil {
d.intra = d.intra[n:]
if err == io.EOF || err == io.ErrUnexpectedEOF {
return nt, nil
} else {
return nt, err
}
}
err = d.push()
if err != nil {
return nt, err
}
d.piece = make([]byte, d.pieceSize)
d.intra = d.piece
}
}
func (d *Digest) Close() error {
if !d.closed {
close(d.pieces)
d.wg.Wait()
}
return nil
}