2017-05-26 15:19:18 +02:00
|
|
|
// Package dbhash implements the dropbox hash as described in
|
|
|
|
//
|
|
|
|
// https://www.dropbox.com/developers/reference/content-hash
|
|
|
|
package dbhash
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/sha256"
|
|
|
|
"hash"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// BlockSize of the checksum in bytes.
|
|
|
|
BlockSize = sha256.BlockSize
|
|
|
|
// Size of the checksum in bytes.
|
|
|
|
Size = sha256.BlockSize
|
|
|
|
bytesPerBlock = 4 * 1024 * 1024
|
|
|
|
hashReturnedError = "hash function returned error"
|
|
|
|
)
|
|
|
|
|
|
|
|
type digest struct {
|
|
|
|
n int // bytes written into blockHash so far
|
|
|
|
blockHash hash.Hash
|
|
|
|
totalHash hash.Hash
|
|
|
|
sumCalled bool
|
|
|
|
writtenMore bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// New returns a new hash.Hash computing the Dropbox checksum.
|
|
|
|
func New() hash.Hash {
|
|
|
|
d := &digest{}
|
|
|
|
d.Reset()
|
|
|
|
return d
|
|
|
|
}
|
|
|
|
|
|
|
|
// writeBlockHash writes the current block hash into the total hash
|
|
|
|
func (d *digest) writeBlockHash() {
|
|
|
|
blockHash := d.blockHash.Sum(nil)
|
|
|
|
_, err := d.totalHash.Write(blockHash)
|
|
|
|
if err != nil {
|
|
|
|
panic(hashReturnedError)
|
|
|
|
}
|
|
|
|
// reset counters for blockhash
|
|
|
|
d.n = 0
|
|
|
|
d.blockHash.Reset()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write writes len(p) bytes from p to the underlying data stream. It returns
|
|
|
|
// the number of bytes written from p (0 <= n <= len(p)) and any error
|
|
|
|
// encountered that caused the write to stop early. Write must return a non-nil
|
|
|
|
// error if it returns n < len(p). Write must not modify the slice data, even
|
|
|
|
// temporarily.
|
|
|
|
//
|
|
|
|
// Implementations must not retain p.
|
|
|
|
func (d *digest) Write(p []byte) (n int, err error) {
|
|
|
|
n = len(p)
|
|
|
|
for len(p) > 0 {
|
|
|
|
d.writtenMore = true
|
build: modernize Go usage
This commit modernizes Go usage. This was done with:
go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -fix -test ./...
Then files needed to be `go fmt`ed and a few comments needed to be
restored.
The modernizations include replacing
- if/else conditional assignment by a call to the built-in min or max functions added in go1.21
- sort.Slice(x, func(i, j int) bool) { return s[i] < s[j] } by a call to slices.Sort(s), added in go1.21
- interface{} by the 'any' type added in go1.18
- append([]T(nil), s...) by slices.Clone(s) or slices.Concat(s), added in go1.21
- loop around an m[k]=v map update by a call to one of the Collect, Copy, Clone, or Insert functions from the maps package, added in go1.21
- []byte(fmt.Sprintf...) by fmt.Appendf(nil, ...), added in go1.19
- append(s[:i], s[i+1]...) by slices.Delete(s, i, i+1), added in go1.21
- a 3-clause for i := 0; i < n; i++ {} loop by for i := range n {}, added in go1.22
2025-02-26 22:08:12 +01:00
|
|
|
toWrite := min(bytesPerBlock-d.n, len(p))
|
2017-05-26 15:19:18 +02:00
|
|
|
_, err = d.blockHash.Write(p[:toWrite])
|
|
|
|
if err != nil {
|
|
|
|
panic(hashReturnedError)
|
|
|
|
}
|
|
|
|
d.n += toWrite
|
|
|
|
p = p[toWrite:]
|
|
|
|
// Accumulate the total hash
|
|
|
|
if d.n == bytesPerBlock {
|
|
|
|
d.writeBlockHash()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return n, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sum appends the current hash to b and returns the resulting slice.
|
|
|
|
// It does not change the underlying hash state.
|
|
|
|
//
|
|
|
|
// TODO(ncw) Sum() can only be called once for this type of hash.
|
|
|
|
// If you call Sum(), then Write() then Sum() it will result in
|
|
|
|
// a panic. Calling Write() then Sum(), then Sum() is OK.
|
|
|
|
func (d *digest) Sum(b []byte) []byte {
|
|
|
|
if d.sumCalled && d.writtenMore {
|
|
|
|
panic("digest.Sum() called more than once")
|
|
|
|
}
|
|
|
|
d.sumCalled = true
|
|
|
|
d.writtenMore = false
|
|
|
|
if d.n != 0 {
|
|
|
|
d.writeBlockHash()
|
|
|
|
}
|
|
|
|
return d.totalHash.Sum(b)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reset resets the Hash to its initial state.
|
|
|
|
func (d *digest) Reset() {
|
|
|
|
d.n = 0
|
|
|
|
d.totalHash = sha256.New()
|
|
|
|
d.blockHash = sha256.New()
|
|
|
|
d.sumCalled = false
|
|
|
|
d.writtenMore = false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Size returns the number of bytes Sum will return.
|
|
|
|
func (d *digest) Size() int {
|
|
|
|
return d.totalHash.Size()
|
|
|
|
}
|
|
|
|
|
|
|
|
// BlockSize returns the hash's underlying block size.
|
|
|
|
// The Write method must be able to accept any amount
|
|
|
|
// of data, but it may operate more efficiently if all writes
|
|
|
|
// are a multiple of the block size.
|
|
|
|
func (d *digest) BlockSize() int {
|
|
|
|
return d.totalHash.BlockSize()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sum returns the Dropbox checksum of the data.
|
|
|
|
func Sum(data []byte) [Size]byte {
|
|
|
|
var d digest
|
|
|
|
d.Reset()
|
2017-05-30 23:08:49 +02:00
|
|
|
_, _ = d.Write(data)
|
2017-05-26 15:19:18 +02:00
|
|
|
var out [Size]byte
|
|
|
|
d.Sum(out[:0])
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
|
|
|
|
// must implement this interface
|
|
|
|
var _ hash.Hash = (*digest)(nil)
|