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)