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. 6package mrhash 7 8import ( 9 "crypto/sha1" 10 "encoding" 11 "encoding/hex" 12 "errors" 13 "hash" 14 "strconv" 15) 16 17const ( 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 27var ( 28 ErrorInvalidHash = errors.New("invalid hash") 29) 30 31type 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. 38func 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. 51func (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. 65func (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 84func 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. 95func (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. 102func (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. 110func (d *digest) BlockSize() int { 111 return BlockSize 112} 113 114// Sum returns the Mailru checksum of the data. 115func 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 123func 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 132var ( 133 _ hash.Hash = (*digest)(nil) 134) 135