github.com/artpar/rclone@v1.67.3/backend/onedrive/quickxorhash/quickxorhash.go (about)

     1  // Package quickxorhash provides the quickXorHash algorithm which is a
     2  // quick, simple non-cryptographic hash algorithm that works by XORing
     3  // the bytes in a circular-shifting fashion.
     4  //
     5  // It is used by Microsoft Onedrive for Business to hash data.
     6  //
     7  // See: https://docs.microsoft.com/en-us/onedrive/developer/code-snippets/quickxorhash
     8  package quickxorhash
     9  
    10  // This code was ported from a fast C-implementation from
    11  // https://github.com/namazso/QuickXorHash
    12  // which has licenced as BSD Zero Clause License
    13  //
    14  // BSD Zero Clause License
    15  //
    16  // Copyright (c) 2022 namazso <admin@namazso.eu>
    17  //
    18  // Permission to use, copy, modify, and/or distribute this software for any
    19  // purpose with or without fee is hereby granted.
    20  //
    21  // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
    22  // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
    23  // AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
    24  // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
    25  // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
    26  // OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
    27  // PERFORMANCE OF THIS SOFTWARE.
    28  
    29  import "hash"
    30  
    31  const (
    32  	// BlockSize is the preferred size for hashing
    33  	BlockSize = 64
    34  	// Size of the output checksum
    35  	Size        = 20
    36  	shift       = 11
    37  	widthInBits = 8 * Size
    38  	dataSize    = shift * widthInBits
    39  )
    40  
    41  type quickXorHash struct {
    42  	data [dataSize]byte
    43  	size uint64
    44  }
    45  
    46  // New returns a new hash.Hash computing the quickXorHash checksum.
    47  func New() hash.Hash {
    48  	return &quickXorHash{}
    49  }
    50  
    51  // Write (via the embedded io.Writer interface) adds more data to the running hash.
    52  // It never returns an error.
    53  //
    54  // Write writes len(p) bytes from p to the underlying data stream. It returns
    55  // the number of bytes written from p (0 <= n <= len(p)) and any error
    56  // encountered that caused the write to stop early. Write must return a non-nil
    57  // error if it returns n < len(p). Write must not modify the slice data, even
    58  // temporarily.
    59  //
    60  // Implementations must not retain p.
    61  func (q *quickXorHash) Write(p []byte) (n int, err error) {
    62  	var i int
    63  	// fill last remain
    64  	lastRemain := q.size % dataSize
    65  	if lastRemain != 0 {
    66  		i += xorBytes(q.data[lastRemain:], p)
    67  	}
    68  
    69  	if i != len(p) {
    70  		for len(p)-i >= dataSize {
    71  			i += xorBytes(q.data[:], p[i:])
    72  		}
    73  		xorBytes(q.data[:], p[i:])
    74  	}
    75  	q.size += uint64(len(p))
    76  	return len(p), nil
    77  }
    78  
    79  // Calculate the current checksum
    80  func (q *quickXorHash) checkSum() (h [Size + 1]byte) {
    81  	for i := 0; i < dataSize; i++ {
    82  		shift := (i * 11) % 160
    83  		shiftBytes := shift / 8
    84  		shiftBits := shift % 8
    85  		shifted := int(q.data[i]) << shiftBits
    86  		h[shiftBytes] ^= byte(shifted)
    87  		h[shiftBytes+1] ^= byte(shifted >> 8)
    88  	}
    89  	h[0] ^= h[20]
    90  
    91  	// XOR the file length with the least significant bits in little endian format
    92  	d := q.size
    93  	h[Size-8] ^= byte(d >> (8 * 0))
    94  	h[Size-7] ^= byte(d >> (8 * 1))
    95  	h[Size-6] ^= byte(d >> (8 * 2))
    96  	h[Size-5] ^= byte(d >> (8 * 3))
    97  	h[Size-4] ^= byte(d >> (8 * 4))
    98  	h[Size-3] ^= byte(d >> (8 * 5))
    99  	h[Size-2] ^= byte(d >> (8 * 6))
   100  	h[Size-1] ^= byte(d >> (8 * 7))
   101  
   102  	return h
   103  }
   104  
   105  // Sum appends the current hash to b and returns the resulting slice.
   106  // It does not change the underlying hash state.
   107  func (q *quickXorHash) Sum(b []byte) []byte {
   108  	hash := q.checkSum()
   109  	return append(b, hash[:Size]...)
   110  }
   111  
   112  // Reset resets the Hash to its initial state.
   113  func (q *quickXorHash) Reset() {
   114  	*q = quickXorHash{}
   115  }
   116  
   117  // Size returns the number of bytes Sum will return.
   118  func (q *quickXorHash) Size() int {
   119  	return Size
   120  }
   121  
   122  // BlockSize returns the hash's underlying block size.
   123  // The Write method must be able to accept any amount
   124  // of data, but it may operate more efficiently if all writes
   125  // are a multiple of the block size.
   126  func (q *quickXorHash) BlockSize() int {
   127  	return BlockSize
   128  }
   129  
   130  // Sum returns the quickXorHash checksum of the data.
   131  func Sum(data []byte) (h [Size]byte) {
   132  	var d quickXorHash
   133  	_, _ = d.Write(data)
   134  	s := d.checkSum()
   135  	copy(h[:], s[:])
   136  	return h
   137  }