diff --git a/backend/onedrive/quickxorhash/quickxorhash.go b/backend/onedrive/quickxorhash/quickxorhash.go new file mode 100644 index 000000000..7fb1bbe07 --- /dev/null +++ b/backend/onedrive/quickxorhash/quickxorhash.go @@ -0,0 +1,203 @@ +// Package quickxorhash provides the quickXorHash algorithm which is a +// quick, simple non-cryptographic hash algorithm that works by XORing +// the bytes in a circular-shifting fashion. +// +// It is used by Microsoft Onedrive for Business to hash data. +// +// See: https://docs.microsoft.com/en-us/onedrive/developer/code-snippets/quickxorhash +package quickxorhash + +// This code was ported from the code snippet linked from +// https://docs.microsoft.com/en-us/onedrive/developer/code-snippets/quickxorhash +// Which has the copyright + +// ------------------------------------------------------------------------------ +// Copyright (c) 2016 Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// ------------------------------------------------------------------------------ + +import ( + "hash" +) + +const ( + // BlockSize is the preferred size for hashing + BlockSize = 64 + // Size of the output checksum + Size = 20 + bitsInLastCell = 32 + shift = 11 + threshold = 600 + widthInBits = 8 * Size + dataSize = (widthInBits-1)/64 + 1 +) + +type quickXorHash struct { + data [dataSize]uint64 + lengthSoFar uint64 + shiftSoFar int +} + +// New returns a new hash.Hash computing the quickXorHash checksum. +func New() hash.Hash { + return &quickXorHash{} +} + +// Write (via the embedded io.Writer interface) adds more data to the running hash. +// It never returns an error. +// +// Write writes len(p) bytes from p to the underlying data stream. It returns +// the number of bytes written from p (0 <= n <= len(p)) and any error +// encountered that caused the write to stop early. Write must return a non-nil +// error if it returns n < len(p). Write must not modify the slice data, even +// temporarily. +// +// Implementations must not retain p. +func (q *quickXorHash) Write(p []byte) (n int, err error) { + currentshift := q.shiftSoFar + + // The bitvector where we'll start xoring + vectorArrayIndex := currentshift / 64 + + // The position within the bit vector at which we begin xoring + vectorOffset := currentshift % 64 + iterations := len(p) + if iterations > widthInBits { + iterations = widthInBits + } + + for i := 0; i < iterations; i++ { + isLastCell := vectorArrayIndex == len(q.data)-1 + var bitsInVectorCell int + if isLastCell { + bitsInVectorCell = bitsInLastCell + } else { + bitsInVectorCell = 64 + } + + // There's at least 2 bitvectors before we reach the end of the array + if vectorOffset <= bitsInVectorCell-8 { + for j := i; j < len(p); j += widthInBits { + q.data[vectorArrayIndex] ^= uint64(p[j]) << uint(vectorOffset) + } + } else { + index1 := vectorArrayIndex + var index2 int + if isLastCell { + index2 = 0 + } else { + index2 = vectorArrayIndex + 1 + } + low := byte(bitsInVectorCell - vectorOffset) + + xoredByte := byte(0) + for j := i; j < len(p); j += widthInBits { + xoredByte ^= p[j] + } + q.data[index1] ^= uint64(xoredByte) << uint(vectorOffset) + q.data[index2] ^= uint64(xoredByte) >> low + } + vectorOffset += shift + for vectorOffset >= bitsInVectorCell { + if isLastCell { + vectorArrayIndex = 0 + } else { + vectorArrayIndex = vectorArrayIndex + 1 + } + vectorOffset -= bitsInVectorCell + } + } + + // Update the starting position in a circular shift pattern + q.shiftSoFar = (q.shiftSoFar + shift*(len(p)%widthInBits)) % widthInBits + + q.lengthSoFar += uint64(len(p)) + + return len(p), nil +} + +// Calculate the current checksum +func (q *quickXorHash) checkSum() (h [Size]byte) { + // Output the data as little endian bytes + ph := 0 + for _, d := range q.data[:len(q.data)-1] { + _ = h[ph+7] // bounds check + h[ph+0] = byte(d >> (8 * 0)) + h[ph+1] = byte(d >> (8 * 1)) + h[ph+2] = byte(d >> (8 * 2)) + h[ph+3] = byte(d >> (8 * 3)) + h[ph+4] = byte(d >> (8 * 4)) + h[ph+5] = byte(d >> (8 * 5)) + h[ph+6] = byte(d >> (8 * 6)) + h[ph+7] = byte(d >> (8 * 7)) + ph += 8 + } + // remaining 32 bits + d := q.data[len(q.data)-1] + h[Size-4] = byte(d >> (8 * 0)) + h[Size-3] = byte(d >> (8 * 1)) + h[Size-2] = byte(d >> (8 * 2)) + h[Size-1] = byte(d >> (8 * 3)) + + // XOR the file length with the least significant bits in little endian format + d = q.lengthSoFar + h[Size-8] ^= byte(d >> (8 * 0)) + h[Size-7] ^= byte(d >> (8 * 1)) + h[Size-6] ^= byte(d >> (8 * 2)) + h[Size-5] ^= byte(d >> (8 * 3)) + h[Size-4] ^= byte(d >> (8 * 4)) + h[Size-3] ^= byte(d >> (8 * 5)) + h[Size-2] ^= byte(d >> (8 * 6)) + h[Size-1] ^= byte(d >> (8 * 7)) + + return h +} + +// Sum appends the current hash to b and returns the resulting slice. +// It does not change the underlying hash state. +func (q *quickXorHash) Sum(b []byte) []byte { + hash := q.checkSum() + return append(b, hash[:]...) +} + +// Reset resets the Hash to its initial state. +func (q *quickXorHash) Reset() { + *q = quickXorHash{} +} + +// Size returns the number of bytes Sum will return. +func (q *quickXorHash) Size() int { + return Size +} + +// BlockSize returns the hash's underlying block size. +// The Write method must be able to accept any amount +// of data, but it may operate more efficiently if all writes +// are a multiple of the block size. +func (q *quickXorHash) BlockSize() int { + return BlockSize +} + +// Sum returns the quickXorHash checksum of the data. +func Sum(data []byte) [Size]byte { + var d quickXorHash + _, _ = d.Write(data) + return d.checkSum() +} diff --git a/backend/onedrive/quickxorhash/quickxorhash_test.go b/backend/onedrive/quickxorhash/quickxorhash_test.go new file mode 100644 index 000000000..94562af2c --- /dev/null +++ b/backend/onedrive/quickxorhash/quickxorhash_test.go @@ -0,0 +1,168 @@ +package quickxorhash + +import ( + "encoding/base64" + "fmt" + "hash" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var testVectors = []struct { + size int + in string + out string +}{ + {0, ``, "AAAAAAAAAAAAAAAAAAAAAAAAAAA="}, + {1, `Sg==`, "SgAAAAAAAAAAAAAAAQAAAAAAAAA="}, + {2, `tbQ=`, "taAFAAAAAAAAAAAAAgAAAAAAAAA="}, + {3, `0pZP`, "0rDEEwAAAAAAAAAAAwAAAAAAAAA="}, + {4, `jRRDVA==`, "jaDAEKgAAAAAAAAABAAAAAAAAAA="}, + {5, `eAV52qE=`, "eChAHrQRCgAAAAAABQAAAAAAAAA="}, + {6, `luBZlaT6`, "lgBHFipBCn0AAAAABgAAAAAAAAA="}, + {7, `qaApEj66lw==`, "qQBFCiTgA11cAgAABwAAAAAAAAA="}, + {8, `/aNzzCFPS/A=`, "/RjFHJgRgicsAR4ACAAAAAAAAAA="}, + {9, `n6Neh7p6fFgm`, "nxiFFw6hCz3wAQsmCQAAAAAAAAA="}, + {10, `J9iPGCbfZSTNyw==`, "J8DGIzBggm+UgQTNUgYAAAAAAAA="}, + {11, `i+UZyUGJKh+ISbk=`, "iyhHBpIRhESo4AOIQ0IuAAAAAAA="}, + {12, `h490d57Pqz5q2rtT`, "h3gEHe7giWeswgdq3MYupgAAAAA="}, + {13, `vPgoDjOfO6fm71RxLw==`, "vMAHChwwg0/s4BTmdQcV4vACAAA="}, + {14, `XoJ1AsoR4fDYJrDqYs4=`, "XhBEHQSgjAiEAx7YPgEs1CEGZwA="}, + {15, `gQaybEqS/4UlDc8e4IJm`, "gDCALNigBEn8oxAlZ8AzPAAOQZg="}, + {16, `2fuxhBJXtpWFe8dOfdGeHw==`, "O9tHLAghgSvYohKFyMMxnNCHaHg="}, + {17, `XBV6YKU9V7yMakZnFIxIkuU=`, "HbplHsBQih5cgReMQYMRzkABRiA="}, + {18, `XJZSOiNO2bmfKnTKD7fztcQX`, "/6ZArHQwAidkIxefQgEdlPGAW8w="}, + {19, `g8VtAh+2Kf4k0kY5tzji2i2zmA==`, "wDNrgwHWAVukwB8kg4YRcnALHIg="}, + {20, `T6LYJIfDh81JrAK309H2JMJTXis=`, "zBTHrspn3mEcohlJdIUAbjGNaNg="}, + {21, `DWAAX5/CIfrmErgZa8ot6ZraeSbu`, "LR2Z0PjuRYGKQB/mhQAuMrAGZbQ="}, + {22, `N9abi3qy/mC1THZuVLHPpx7SgwtLOA==`, "1KTYttCBEen8Hwy1doId3ECFWDw="}, + {23, `LlUe7wHerLqEtbSZLZgZa9u0m7hbiFs=`, "TqVZpxs3cN61BnuFvwUtMtECTGQ="}, + {24, `bU2j/0XYdgfPFD4691jV0AOUEUPR4Z5E`, "bnLBiLpVgnxVkXhNsIAPdHAPLFQ="}, + {25, `lScPwPsyUsH2T1Qsr31wXtP55Wqbe47Uyg==`, "VDMSy8eI26nBHCB0e8gVWPCKPsA="}, + {26, `rJaKh1dLR1k+4hynliTZMGf8Nd4qKKoZiAM=`, "r7bjwkl8OYQeNaMcCY8fTmEJEmQ="}, + {27, `pPsT0CPmHrd3Frsnva1pB/z1ytARLeHEYRCo`, "Rdg7rCcDomL59pL0s6GuTvqLVqQ="}, + {28, `wSRChaqmrsnMrfB2yqI43eRWbro+f9kBvh+01w==`, "YTtloIi6frI7HX3vdLvE7I2iUOA="}, + {29, `apL67KMIRxQeE9k1/RuW09ppPjbF1WeQpTjSWtI=`, "CIpedls+ZlSQ654fl+X26+Q7LVU="}, + {30, `53yx0/QgMTVb7OOzHRHbkS7ghyRc+sIXxi7XHKgT`, "zfJtLGFgR9DB3Q64fAFIp+S5iOY="}, + {31, `PwXNnutoLLmxD8TTog52k8cQkukmT87TTnDipKLHQw==`, "PTaGs7yV3FUyBy/SfU6xJRlCJlI="}, + {32, `NbYXsp5/K6mR+NmHwExjvWeWDJFnXTKWVlzYHoesp2E=`, "wjuAuWDiq04qDt1R8hHWDDcwVoQ="}, + {33, `qQ70RB++JAR5ljNv3lJt1PpqETPsckopfonItu18Cr3E`, "FkJaeg/0Z5+euShYlLpE2tJh+Lo="}, + {34, `RhzSatQTQ9/RFvpHyQa1WLdkr3nIk6MjJUma998YRtp44A==`, "SPN2D29reImAqJezlqV2DLbi8tk="}, + {35, `DND1u1uZ5SqZVpRUk6NxSUdVo7IjjL9zs4A1evDNCDLcXWc=`, "S6lBk2hxI2SWBfn7nbEl7D19UUs="}, + {36, `jEi62utFz69JMYHjg1iXy7oO6ZpZSLcVd2B+pjm6BGsv/CWi`, "s0lYU9tr/bp9xsnrrjYgRS5EvV8="}, + {37, `hfS3DZZnhy0hv7nJdXLv/oJOtIgAuP9SInt/v8KeuO4/IvVh4A==`, "CV+HQCdd2A/e/vdi12f2UU55GLA="}, + {38, `EkPQAC6ymuRrYjIXD/LT/4Vb+7aTjYVZOHzC8GPCEtYDP0+T3Nc=`, "kE9H9sEmr3vHBYUiPbvsrcDgSEo="}, + {39, `vtBOGIENG7yQ/N7xNWPNIgy66Gk/I2Ur/ZhdFNUK9/1FCZuu/KeS`, "+Fgp3HBimtCzUAyiinj3pkarYTk="}, + {40, `YnF4smoy9hox2jBlJ3VUa4qyCRhOZbWcmFGIiszTT4zAdYHsqJazyg==`, "arkIn+ELddmE8N34J9ydyFKW+9w="}, + {41, `0n7nl3YJtipy6yeUbVPWtc2h45WbF9u8hTz5tNwj3dZZwfXWkk+GN3g=`, "YJLNK7JR64j9aODWfqDvEe/u6NU="}, + {42, `FnIIPHayc1pHkY4Lh8+zhWwG8xk6Knk/D3cZU1/fOUmRAoJ6CeztvMOL`, "22RPOylMtdk7xO/QEQiMli4ql0k="}, + {43, `J82VT7ND0Eg1MorSfJMUhn+qocF7PsUpdQAMrDiHJ2JcPZAHZ2nyuwjoKg==`, "pOR5eYfwCLRJbJsidpc1rIJYwtM="}, + {44, `Zbu+78+e35ZIymV5KTDdub5McyI3FEO8fDxs62uWHQ9U3Oh3ZqgaZ30SnmQ=`, "DbvbTkgNTgWRqRidA9r1jhtUjro="}, + {45, `lgybK3Da7LEeY5aeeNrqcdHvv6mD1W4cuQ3/rUj2C/CNcSI0cAMw6vtpVY3y`, "700RQByn1lRQSSme9npQB/Ye+bY="}, + {46, `jStZgKHv4QyJLvF2bYbIUZi/FscHALfKHAssTXkrV1byVR9eACwW9DNZQRHQwg==`, "uwN55He8xgE4g93dH9163xPew4U="}, + {47, `V1PSud3giF5WW72JB/bgtltsWtEB5V+a+wUALOJOGuqztzVXUZYrvoP3XV++gM0=`, "U+3ZfUF/6mwOoHJcSHkQkckfTDA="}, + {48, `VXs4t4tfXGiWAL6dlhEMm0YQF0f2w9rzX0CvIVeuW56o6/ec2auMpKeU2VeteEK5`, "sq24lSf7wXLH8eigHl07X+qPTps="}, + {49, `bLUn3jLH+HFUsG3ptWTHgNvtr3eEv9lfKBf0jm6uhpqhRwtbEQ7Ovj/hYQf42zfdtQ==`, "uC8xrnopGiHebGuwgq607WRQyxQ="}, + {50, `4SVmjtXIL8BB8SfkbR5Cpaljm2jpyUfAhIBf65XmKxHlz9dy5XixgiE/q1lv+esZW/E=`, "wxZ0rxkMQEnRNAp8ZgEZLT4RdLM="}, + {51, `pMljctlXeFUqbG3BppyiNbojQO3ygg6nZPeUZaQcVyJ+Clgiw3Q8ntLe8+02ZSfyCc39`, "aZEPmNvOXnTt7z7wt+ewV7QGMlg="}, + {52, `C16uQlxsHxMWnV2gJhFPuJ2/guZ4N1YgmNvAwL1yrouGQtwieGx8WvZsmYRnX72JnbVtTw==`, "QtlSNqXhVij64MMhKJ3EsDFB/z8="}, + {53, `7ZVDOywvrl3L0GyKjjcNg2CcTI81n2CeUbzdYWcZOSCEnA/xrNHpiK01HOcGh3BbxuS4S6g=`, "4NznNJc4nmXeApfiCFTq/H5LbHw="}, + {54, `JXm2tTVqpYuuz2Cc+ZnPusUb8vccPGrzWK2oVwLLl/FjpFoxO9FxGlhnB08iu8Q/XQSdzHn+`, "IwE5+2pKNcK366I2k2BzZYPibSI="}, + {55, `TiiU1mxzYBSGZuE+TX0l9USWBilQ7dEml5lLrzNPh75xmhjIK8SGqVAkvIMgAmcMB+raXdMPZg==`, "yECGHtgR128ScP4XlvF96eLbIBE="}, + {56, `zz+Q4zi6wh0fCJUFU9yUOqEVxlIA93gybXHOtXIPwQQ44pW4fyh6BRgc1bOneRuSWp85hwlTJl8=`, "+3Ef4D6yuoC8J+rbFqU1cegverE="}, + {57, `sa6SHK9z/G505bysK5KgRO2z2cTksDkLoFc7sv0tWBmf2G2mCiozf2Ce6EIO+W1fRsrrtn/eeOAV`, "xZg1CwMNAjN0AIXw2yh4+1N3oos="}, + {58, `0qx0xdyTHhnKJ22IeTlAjRpWw6y2sOOWFP75XJ7cleGJQiV2kyrmQOST4DGHIL0qqA7sMOdzKyTV +iw==`, "bS0tRYPkP1Gfc+ZsBm9PMzPunG8="}, + {59, `QuzaF0+5ooig6OLEWeibZUENl8EaiXAQvK9UjBEauMeuFFDCtNcGs25BDtJGGbX90gH4VZvCCDNC +q4s=`, "rggokuJq1OGNOfB6aDp2g4rdPgw="}, + {60, `+wg2x23GZQmMLkdv9MeAdettIWDmyK6Wr+ba23XD+Pvvq1lIMn9QIQT4Z7QHJE3iC/ZMFgaId9VA +yY3d`, "ahQbTmOdiKUNdhYRHgv5/Ky+Y6k="}, + {61, `y0ydRgreRQwP95vpNP92ioI+7wFiyldHRbr1SfoPNdbKGFA0lBREaBEGNhf9yixmfE+Azo2AuROx +b7Yc7g==`, "cJKFc0dXfiN4hMg1lcMf5E4gqvo="}, + {62, `LxlVvGXSQlSubK8r0pGf9zf7s/3RHe75a2WlSXQf3gZFR/BtRnR7fCIcaG//CbGfodBFp06DBx/S +9hUV8Bk=`, "NwuwhhRWX8QZ/vhWKWgQ1+rNomI="}, + {63, `L+LSB8kmGMnHaWVA5P/+qFnfQliXvgJW7d2JGAgT6+koi5NQujFW1bwQVoXrBVyob/gBxGizUoJM +gid5gGNo`, "ndX/KZBtFoeO3xKeo1ajO/Jy+rY="}, + {64, `Mb7EGva2rEE5fENDL85P+BsapHEEjv2/siVhKjvAQe02feExVOQSkfmuYzU/kTF1MaKjPmKF/w+c +bvwfdWL8aQ==`, "n1anP5NfvD4XDYWIeRPW3ZkPv1Y="}, + {111, `jyibxJSzO6ZiZ0O1qe3tG/bvIAYssvukh9suIT5wEy1JBINVgPiqdsTW0cOpP0aUfP7mgqLfADkz +I/m/GgCuVhr8oFLrOCoTx1/psBOWwhltCbhUx51Icm9aH8tY4Z3ccU+6BKpYQkLCy0B/A9Zc`, "hZfLIilSITC6N3e3tQ/iSgEzkto="}, + {128, `ikwCorI7PKWz17EI50jZCGbV9JU2E8bXVfxNMg5zdmqSZ2NlsQPp0kqYIPjzwTg1MBtfWPg53k0h +0P2naJNEVgrqpoHTfV2b3pJ4m0zYPTJmUX4Bg/lOxcnCxAYKU29Y5F0U8Quz7ZXFBEweftXxJ7RS +4r6N7BzJrPsLhY7hgck=`, "imAoFvCWlDn4yVw3/oq1PDbbm6U="}, + {222, `PfxMcUd0vIW6VbHG/uj/Y0W6qEoKmyBD0nYebEKazKaKG+UaDqBEcmQjbfQeVnVLuodMoPp7P7TR +1htX5n2VnkHh22xDyoJ8C/ZQKiSNqQfXvh83judf4RVr9exJCud8Uvgip6aVZTaPrJHVjQhMCp/d +EnGvqg0oN5OVkM2qqAXvA0teKUDhgNM71sDBVBCGXxNOR2bpbD1iM4dnuT0ey4L+loXEHTL0fqMe +UcEi2asgImnlNakwenDzz0x57aBwyq3AspCFGB1ncX4yYCr/OaCcS5OKi/00WH+wNQU3`, "QX/YEpG0gDsmhEpCdWhsxDzsfVE="}, + {256, `qwGf2ESubE5jOUHHyc94ORczFYYbc2OmEzo+hBIyzJiNwAzC8PvJqtTzwkWkSslgHFGWQZR2BV5+ +uYTrYT7HVwRM40vqfj0dBgeDENyTenIOL1LHkjtDKoXEnQ0mXAHoJ8PjbNC93zi5TovVRXTNzfGE +s5dpWVqxUzb5lc7dwkyvOluBw482mQ4xrzYyIY1t+//OrNi1ObGXuUw2jBQOFfJVj2Y6BOyYmfB1 +y36eBxi3zxeG5d5NYjm2GSh6e08QMAwu3zrINcqIzLOuNIiGXBtl7DjKt7b5wqi4oFiRpZsCyx2s +mhSrdrtK/CkdU6nDN+34vSR/M8rZpWQdBE7a8g==`, "WYT9JY3JIo/pEBp+tIM6Gt2nyTM="}, + {333, `w0LGhqU1WXFbdavqDE4kAjEzWLGGzmTNikzqnsiXHx2KRReKVTxkv27u3UcEz9+lbMvYl4xFf2Z4 +aE1xRBBNd1Ke5C0zToSaYw5o4B/7X99nKK2/XaUX1byLow2aju2XJl2OpKpJg+tSJ2fmjIJTkfuY +Uz574dFX6/VXxSxwGH/xQEAKS5TCsBK3CwnuG1p5SAsQq3gGVozDWyjEBcWDMdy8/AIFrj/y03Lf +c/RNRCQTAfZbnf2QwV7sluw4fH3XJr07UoD0YqN+7XZzidtrwqMY26fpLZnyZjnBEt1FAZWO7RnK +G5asg8xRk9YaDdedXdQSJAOy6bWEWlABj+tVAigBxavaluUH8LOj+yfCFldJjNLdi90fVHkUD/m4 +Mr5OtmupNMXPwuG3EQlqWUVpQoYpUYKLsk7a5Mvg6UFkiH596y5IbJEVCI1Kb3D1`, "e3+wo77iKcILiZegnzyUNcjCdoQ="}, +} + +func TestQuickXorHash(t *testing.T) { + for _, test := range testVectors { + what := fmt.Sprintf("test size %d", test.size) + in, err := base64.StdEncoding.DecodeString(test.in) + require.NoError(t, err, what) + got := Sum(in) + want, err := base64.StdEncoding.DecodeString(test.out) + require.NoError(t, err, what) + assert.Equal(t, want, got[:], what) + } +} + +func TestQuickXorHashByBlock(t *testing.T) { + for _, blockSize := range []int{1, 2, 4, 7, 8, 16, 32, 64, 128, 256, 512} { + for _, test := range testVectors { + what := fmt.Sprintf("test size %d blockSize %d", test.size, blockSize) + in, err := base64.StdEncoding.DecodeString(test.in) + require.NoError(t, err, what) + h := New() + for i := 0; i < len(in); i += blockSize { + end := i + blockSize + if end > len(in) { + end = len(in) + } + n, err := h.Write(in[i:end]) + require.Equal(t, end-i, n, what) + require.NoError(t, err, what) + } + got := h.Sum(nil) + want, err := base64.StdEncoding.DecodeString(test.out) + require.NoError(t, err, what) + assert.Equal(t, want, got[:], test.size, what) + } + } +} + +func TestSize(t *testing.T) { + d := New() + assert.Equal(t, 20, d.Size()) +} + +func TestBlockSize(t *testing.T) { + d := New() + assert.Equal(t, 64, d.BlockSize()) +} + +func TestReset(t *testing.T) { + d := New() + zeroHash := d.Sum(nil) + _, _ = d.Write([]byte{1}) + assert.NotEqual(t, zeroHash, d.Sum(nil)) + d.Reset() + assert.Equal(t, zeroHash, d.Sum(nil)) +} + +// check interface +var _ hash.Hash = (*quickXorHash)(nil)