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  )