github.com/artpar/rclone@v1.67.3/backend/mailru/mrhash/mrhash.go (about) 1 // Package mrhash implements the mailru hash, which is a modified SHA1. 2 // If file size is less than or equal to the SHA1 block size (20 bytes), 3 // its hash is simply its data right-padded with zero bytes. 4 // Hash sum of a larger file is computed as a SHA1 sum of the file data 5 // bytes concatenated with a decimal representation of the data length. 6 package mrhash 7 8 import ( 9 "crypto/sha1" 10 "encoding" 11 "encoding/hex" 12 "errors" 13 "hash" 14 "strconv" 15 ) 16 17 const ( 18 // BlockSize of the checksum in bytes. 19 BlockSize = sha1.BlockSize 20 // Size of the checksum in bytes. 21 Size = sha1.Size 22 startString = "mrCloud" 23 hashError = "hash function returned error" 24 ) 25 26 // Global errors 27 var ( 28 ErrorInvalidHash = errors.New("invalid hash") 29 ) 30 31 type digest struct { 32 total int // bytes written into hash so far 33 sha hash.Hash // underlying SHA1 34 small []byte // small content 35 } 36 37 // New returns a new hash.Hash computing the Mailru checksum. 38 func New() hash.Hash { 39 d := &digest{} 40 d.Reset() 41 return d 42 } 43 44 // Write writes len(p) bytes from p to the underlying data stream. It returns 45 // the number of bytes written from p (0 <= n <= len(p)) and any error 46 // encountered that caused the write to stop early. Write must return a non-nil 47 // error if it returns n < len(p). Write must not modify the slice data, even 48 // temporarily. 49 // 50 // Implementations must not retain p. 51 func (d *digest) Write(p []byte) (n int, err error) { 52 n, err = d.sha.Write(p) 53 if err != nil { 54 panic(hashError) 55 } 56 d.total += n 57 if d.total <= Size { 58 d.small = append(d.small, p...) 59 } 60 return n, nil 61 } 62 63 // Sum appends the current hash to b and returns the resulting slice. 64 // It does not change the underlying hash state. 65 func (d *digest) Sum(b []byte) []byte { 66 // If content is small, return it padded to Size 67 if d.total <= Size { 68 padded := make([]byte, Size) 69 copy(padded, d.small) 70 return append(b, padded...) 71 } 72 endString := strconv.Itoa(d.total) 73 copy, err := cloneSHA1(d.sha) 74 if err == nil { 75 _, err = copy.Write([]byte(endString)) 76 } 77 if err != nil { 78 panic(hashError) 79 } 80 return copy.Sum(b) 81 } 82 83 // cloneSHA1 clones state of SHA1 hash 84 func cloneSHA1(orig hash.Hash) (clone hash.Hash, err error) { 85 state, err := orig.(encoding.BinaryMarshaler).MarshalBinary() 86 if err != nil { 87 return nil, err 88 } 89 clone = sha1.New() 90 err = clone.(encoding.BinaryUnmarshaler).UnmarshalBinary(state) 91 return 92 } 93 94 // Reset resets the Hash to its initial state. 95 func (d *digest) Reset() { 96 d.sha = sha1.New() 97 _, _ = d.sha.Write([]byte(startString)) 98 d.total = 0 99 } 100 101 // Size returns the number of bytes Sum will return. 102 func (d *digest) Size() int { 103 return Size 104 } 105 106 // BlockSize returns the hash's underlying block size. 107 // The Write method must be able to accept any amount 108 // of data, but it may operate more efficiently if all writes 109 // are a multiple of the block size. 110 func (d *digest) BlockSize() int { 111 return BlockSize 112 } 113 114 // Sum returns the Mailru checksum of the data. 115 func Sum(data []byte) []byte { 116 var d digest 117 d.Reset() 118 _, _ = d.Write(data) 119 return d.Sum(nil) 120 } 121 122 // DecodeString converts a string to the Mailru hash 123 func DecodeString(s string) ([]byte, error) { 124 b, err := hex.DecodeString(s) 125 if err != nil || len(b) != Size { 126 return nil, ErrorInvalidHash 127 } 128 return b, nil 129 } 130 131 // must implement this interface 132 var ( 133 _ hash.Hash = (*digest)(nil) 134 )