github.com/artpar/rclone@v1.67.3/backend/dropbox/dbhash/dbhash.go (about) 1 // Package dbhash implements the dropbox hash as described in 2 // 3 // https://www.dropbox.com/developers/reference/content-hash 4 package dbhash 5 6 import ( 7 "crypto/sha256" 8 "hash" 9 ) 10 11 const ( 12 // BlockSize of the checksum in bytes. 13 BlockSize = sha256.BlockSize 14 // Size of the checksum in bytes. 15 Size = sha256.BlockSize 16 bytesPerBlock = 4 * 1024 * 1024 17 hashReturnedError = "hash function returned error" 18 ) 19 20 type digest struct { 21 n int // bytes written into blockHash so far 22 blockHash hash.Hash 23 totalHash hash.Hash 24 sumCalled bool 25 writtenMore bool 26 } 27 28 // New returns a new hash.Hash computing the Dropbox checksum. 29 func New() hash.Hash { 30 d := &digest{} 31 d.Reset() 32 return d 33 } 34 35 // writeBlockHash writes the current block hash into the total hash 36 func (d *digest) writeBlockHash() { 37 blockHash := d.blockHash.Sum(nil) 38 _, err := d.totalHash.Write(blockHash) 39 if err != nil { 40 panic(hashReturnedError) 41 } 42 // reset counters for blockhash 43 d.n = 0 44 d.blockHash.Reset() 45 } 46 47 // Write writes len(p) bytes from p to the underlying data stream. It returns 48 // the number of bytes written from p (0 <= n <= len(p)) and any error 49 // encountered that caused the write to stop early. Write must return a non-nil 50 // error if it returns n < len(p). Write must not modify the slice data, even 51 // temporarily. 52 // 53 // Implementations must not retain p. 54 func (d *digest) Write(p []byte) (n int, err error) { 55 n = len(p) 56 for len(p) > 0 { 57 d.writtenMore = true 58 toWrite := bytesPerBlock - d.n 59 if toWrite > len(p) { 60 toWrite = len(p) 61 } 62 _, err = d.blockHash.Write(p[:toWrite]) 63 if err != nil { 64 panic(hashReturnedError) 65 } 66 d.n += toWrite 67 p = p[toWrite:] 68 // Accumulate the total hash 69 if d.n == bytesPerBlock { 70 d.writeBlockHash() 71 } 72 } 73 return n, nil 74 } 75 76 // Sum appends the current hash to b and returns the resulting slice. 77 // It does not change the underlying hash state. 78 // 79 // TODO(ncw) Sum() can only be called once for this type of hash. 80 // If you call Sum(), then Write() then Sum() it will result in 81 // a panic. Calling Write() then Sum(), then Sum() is OK. 82 func (d *digest) Sum(b []byte) []byte { 83 if d.sumCalled && d.writtenMore { 84 panic("digest.Sum() called more than once") 85 } 86 d.sumCalled = true 87 d.writtenMore = false 88 if d.n != 0 { 89 d.writeBlockHash() 90 } 91 return d.totalHash.Sum(b) 92 } 93 94 // Reset resets the Hash to its initial state. 95 func (d *digest) Reset() { 96 d.n = 0 97 d.totalHash = sha256.New() 98 d.blockHash = sha256.New() 99 d.sumCalled = false 100 d.writtenMore = false 101 } 102 103 // Size returns the number of bytes Sum will return. 104 func (d *digest) Size() int { 105 return d.totalHash.Size() 106 } 107 108 // BlockSize returns the hash's underlying block size. 109 // The Write method must be able to accept any amount 110 // of data, but it may operate more efficiently if all writes 111 // are a multiple of the block size. 112 func (d *digest) BlockSize() int { 113 return d.totalHash.BlockSize() 114 } 115 116 // Sum returns the Dropbox checksum of the data. 117 func Sum(data []byte) [Size]byte { 118 var d digest 119 d.Reset() 120 _, _ = d.Write(data) 121 var out [Size]byte 122 d.Sum(out[:0]) 123 return out 124 } 125 126 // must implement this interface 127 var _ hash.Hash = (*digest)(nil)