Commit a0e3a745 authored by Ilya Maykov's avatar Ilya Maykov Committed by Facebook Github Bot

Implemented LtHash in folly/experimental/crypto

Summary:
Added LtHash, a cryptographic homomorphic hash, to folly/experimental/crypto.
This has a soft dependency on libsodium and the code will not be compiled if libsodium is not detected by cmake.

Reviewed By: djwatson

Differential Revision: D13390825

fbshipit-source-id: f7597ced7bcc7b403e8bbaa733837b795128f1b3
parent f42dd787
......@@ -170,12 +170,52 @@ if (NOT ${LIBAIO_FOUND})
${FOLLY_DIR}/experimental/io/AsyncIO.h
)
endif()
if (NOT ${LIBSODIUM_FOUND})
if (${LIBSODIUM_FOUND})
string(FIND ${CMAKE_LIBRARY_ARCHITECTURE} "x86_64" IS_X86_64_ARCH)
if (${IS_X86_64_ARCH} STREQUAL "-1")
message(
STATUS
"arch ${CMAKE_LIBRARY_ARCHITECTURE} does not match x86_64, "
"skipping setting SSE2/AVX2 compile flags for LtHash SIMD code"
)
else()
message(
STATUS
"arch ${CMAKE_LIBRARY_ARCHITECTURE} matches x86_64, "
"setting SSE2/AVX2 compile flags for LtHash SIMD code"
)
set_source_files_properties(
${FOLLY_DIR}/experimental/crypto/detail/MathOperation_AVX2.cpp
PROPERTIES
COMPILE_FLAGS
-mavx -mavx2 -msse2
)
set_source_files_properties(
${FOLLY_DIR}/experimental/crypto/detail/MathOperation_Simple.cpp
PROPERTIES
COMPILE_FLAGS
-mno-avx -mno-avx2 -mno-sse2
)
set_source_files_properties(
${FOLLY_DIR}/experimental/crypto/detail/MathOperation_SSE2.cpp
PROPERTIES
COMPILE_FLAGS
-mno-avx -mno-avx2 -msse2
)
endif()
else()
list(REMOVE_ITEM files
${FOLLY_DIR}/experimental/crypto/Blake2xb.cpp
${FOLLY_DIR}/experimental/crypto/detail/MathOperation_AVX2.cpp
${FOLLY_DIR}/experimental/crypto/detail/MathOperation_Simple.cpp
${FOLLY_DIR}/experimental/crypto/detail/MathOperation_SSE2.cpp
${FOLLY_DIR}/experimental/crypto/LtHash.cpp
)
list(REMOVE_ITEM hfiles
${FOLLY_DIR}/experimental/crypto/Blake2xb.h
${FOLLY_DIR}/experimental/crypto/detail/LtHashInternal.h
${FOLLY_DIR}/experimental/crypto/LtHash-inl.h
${FOLLY_DIR}/experimental/crypto/LtHash.h
)
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
......
/*
* Copyright 2017-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/lang/Bits.h>
#include <sodium.h>
#include <cstring>
#include <stdexcept>
#include <folly/experimental/crypto/detail/LtHashInternal.h>
namespace folly {
namespace crypto {
namespace detail {
/**
* Implements bit twiddling operations for elements of size B bits.
* Currently there are specializations for B = 16, B = 20, and B = 32.
* All operations are performed on groups of elements packed into uint64_t
* operands.
*
* When B == 16, each uint64_t contains 4 elements without any padding bits.
* Both SSE2 and AVX2 have native support for adding vectors of 16-bit ints
* so we can use those directly. When not using SSE2 or AVX2, there is some
* minor inefficiency because the odd and even elements of each 64-bit block
* need to be added separately, then XORed together.
* The packed int looks like:
* <16 bits of data> <16 bits of data> <16 bits of data> <16 bits of data>.
*
* When B == 20, each uint64_t contains 3 elements with 0 padding bits at
* 0-based positions 63, 62, 41, and 20. The packed int looks like:
* 00 <20 bits of data> 0 <20 bits of data> 0 <20 bits of data>.
*
* When B == 32, each uint64_t contains 2 elements without any padding bits.
* Both SSE2 and AVX2 have native support for adding vectors of 32-bit ints
* so we can use those directly. When not using SSE2 or AVX2, there is some
* minor inefficiency because the high and low elements of each 64-bit block
* need to be added separately, then XORed together.
* The packed int looks like:
* <32 bits of data> <32 bits of data>.
*/
template <std::size_t B>
struct Bits {
static inline constexpr uint64_t kDataMask();
static inline constexpr bool needsPadding();
};
////// Template specialization for B = 16
// static
template <>
inline constexpr uint64_t Bits<16>::kDataMask() {
return 0xffffffffffffffffULL;
}
// static
template <>
inline constexpr bool Bits<16>::needsPadding() {
return false;
}
////// Template specialization for B = 20
// static
template <>
inline constexpr uint64_t Bits<20>::kDataMask() {
// In binary this mask looks like:
// 00 <1 repeated 20 times> 0 <1 repeated 20 times> 0 <1 repeated 20 times>
return ~0xC000020000100000ULL;
}
// static
template <>
inline constexpr bool Bits<20>::needsPadding() {
return true;
}
////// Template specialization for B = 32
// static
template <>
inline constexpr uint64_t Bits<32>::kDataMask() {
return 0xffffffffffffffffULL;
}
// static
template <>
inline constexpr bool Bits<32>::needsPadding() {
return false;
}
/* static */
template <std::size_t B>
constexpr size_t getElementsPerUint64() {
// how many elements fit into a 64-bit int? If padding is needed, assumes that
// there is 1 padding bit between elements and any partial space is not used.
// If padding is not needed, the computation is a trivial division.
return detail::Bits<B>::needsPadding() ? ((sizeof(uint64_t) * 8) / (B + 1))
: ((sizeof(uint64_t) * 8) / B);
}
// Compile-time computation of the checksum size for a hash with given B and N.
template <std::size_t B, std::size_t N>
constexpr size_t getChecksumSizeBytes() {
constexpr size_t elemsPerUint64 = getElementsPerUint64<B>();
static_assert(
N % elemsPerUint64 == 0,
"Invalid parameters: N %% elemsPerUint64 must be 0");
return (N / elemsPerUint64) * sizeof(uint64_t);
}
} // namespace detail
template <std::size_t B, std::size_t N>
LtHash<B, N>::LtHash(const folly::IOBuf& initialChecksum) : checksum_{} {
static_assert(N > 999, "element count must be at least 1000");
static_assert(B == 16 || B == 20 || B == 32,
"invalid element size in bits, must be one of: [ 16, 20, 32 ]");
// Make sure libsodium is initialized, but only do it once.
static const int sodiumInitResult = []() { return sodium_init(); }();
if (sodiumInitResult == -1) {
throw std::runtime_error("sodium_init() failed");
}
if (initialChecksum.length() == 0) {
checksum_ = detail::allocateCacheAlignedIOBuf(getChecksumSizeBytes());
checksum_.append(getChecksumSizeBytes());
reset();
} else {
setChecksum(initialChecksum);
}
}
template <std::size_t B, std::size_t N>
LtHash<B, N>::LtHash(std::unique_ptr<folly::IOBuf> initialChecksum)
: checksum_{} {
// Make sure libsodium is initialized, but only do it once.
static const int sodiumInitResult = []() { return sodium_init(); }();
if (sodiumInitResult == -1) {
throw std::runtime_error("sodium_init() failed");
}
setChecksum(std::move(initialChecksum));
}
template <std::size_t B, std::size_t N>
LtHash<B, N>::LtHash(const LtHash<B, N>& that) : checksum_{} {
// Note: we don't need to initialize libsodium in the copy constructor, since
// before a copy constructor is called, at least one object of this type must
// be constructed without using a copy constructor, so we know that libsodium
// must have been initialized already.
setChecksum(that.checksum_);
}
template <std::size_t B, std::size_t N>
LtHash<B, N>& LtHash<B, N>::operator=(const LtHash<B, N>& that) {
if (checksum_.length() == that.checksum_.length()) {
std::memcpy(
checksum_.writableData(), that.checksum_.data(), checksum_.length());
} else {
// this probably means that this object was moved away from and
// checksum_.length() is 0, so we need to allocate a new checksum_ and
// copy the contents.
setChecksum(that.checksum_);
}
return *this;
}
template <std::size_t B, std::size_t N>
LtHash<B, N>& LtHash<B, N>::operator+=(const LtHash<B, N>& rhs) {
detail::MathOperation<detail::MathEngine::AUTO>::add(
detail::Bits<B>::kDataMask(),
B,
{checksum_.data(), checksum_.length()},
{rhs.checksum_.data(), rhs.checksum_.length()},
{checksum_.writableData(), checksum_.length()});
return *this;
}
template <std::size_t B, std::size_t N>
LtHash<B, N>& LtHash<B, N>::operator-=(const LtHash<B, N>& rhs) {
detail::MathOperation<detail::MathEngine::AUTO>::sub(
detail::Bits<B>::kDataMask(),
B,
{checksum_.data(), checksum_.length()},
{rhs.checksum_.data(), rhs.checksum_.length()},
{checksum_.writableData(), checksum_.length()});
return *this;
}
template <std::size_t B, std::size_t N>
bool LtHash<B, N>::operator==(const LtHash<B, N>& that) const {
if (this == &that) { // same memory location means it's the same object
return true;
} else if (this->checksum_.length() != that.checksum_.length()) {
return false;
} else if (this->checksum_.length() == 0) {
// both objects must have been moved away from
return true;
} else {
int cmp = sodium_memcmp(
this->checksum_.data(),
that.checksum_.data(),
this->checksum_.length());
return cmp == 0;
}
}
template <std::size_t B, std::size_t N>
bool LtHash<B, N>::checksumEquals(folly::ByteRange otherChecksum) const {
if (otherChecksum.size() != getChecksumSizeBytes()) {
throw std::runtime_error("Invalid checksum size");
} else if (this->checksum_.length() != otherChecksum.size()) {
return false;
} else {
int cmp = sodium_memcmp(
this->checksum_.data(),
otherChecksum.data(),
this->checksum_.length());
return cmp == 0;
}
}
template <std::size_t B, std::size_t N>
bool LtHash<B, N>::operator!=(const LtHash<B, N>& that) const {
return !(*this == that);
}
template <std::size_t B, std::size_t N>
void LtHash<B, N>::reset() {
std::memset(checksum_.writableData(), 0, checksum_.length());
}
template <std::size_t B, std::size_t N>
void LtHash<B, N>::setChecksum(const folly::IOBuf& checksum) {
if (checksum.computeChainDataLength() != getChecksumSizeBytes()) {
throw std::runtime_error("Invalid checksum size");
}
folly::IOBuf checksumCopy =
detail::allocateCacheAlignedIOBuf(getChecksumSizeBytes());
for (auto range : checksum) {
std::memcpy(checksumCopy.writableTail(), range.data(), range.size());
checksumCopy.append(range.size());
}
if /* constexpr */ (detail::Bits<B>::needsPadding()) {
bool isPaddedCorrectly =
detail::MathOperation<detail::MathEngine::AUTO>::checkPaddingBits(
detail::Bits<B>::kDataMask(),
{checksumCopy.data(), checksumCopy.length()});
if (!isPaddedCorrectly) {
throw std::runtime_error("Invalid checksum has non-0 padding bits");
}
}
checksum_ = std::move(checksumCopy);
}
template <std::size_t B, std::size_t N>
void LtHash<B, N>::setChecksum(std::unique_ptr<folly::IOBuf> checksum) {
if (checksum == nullptr) {
throw std::runtime_error("null checksum");
}
// If the checksum is not eligible for move, call the copy version
if (checksum->isChained() ||
checksum->isShared() ||
!detail::isCacheAlignedAddress(checksum->data())) {
setChecksum(*checksum);
return;
}
if (checksum->computeChainDataLength() != getChecksumSizeBytes()) {
throw std::runtime_error("Invalid checksum size");
}
// If we get here, we know that the input is not null, shared, or chained,
// is the proper size, and is aligned on a cache line boundary.
// Just need to check the padding bits before taking ownership of the buffer.
if /* constexpr */ (detail::Bits<B>::needsPadding()) {
bool isPaddedCorrectly =
detail::MathOperation<detail::MathEngine::AUTO>::checkPaddingBits(
detail::Bits<B>::kDataMask(),
{checksum->data(), checksum->length()});
if (!isPaddedCorrectly) {
throw std::runtime_error("Invalid checksum has non-0 padding bits");
}
}
checksum_ = std::move(*checksum);
}
template <std::size_t B, std::size_t N>
template <typename... Args>
void LtHash<B, N>::hashObject(
folly::MutableByteRange out,
folly::ByteRange firstRange,
Args&&... moreRanges) {
CHECK_EQ(getChecksumSizeBytes(), out.size());
Blake2xb digest;
digest.init(out.size());
updateDigest(digest, firstRange, std::forward<Args>(moreRanges)...);
digest.finish(out);
if /* constexpr */ (detail::Bits<B>::needsPadding()) {
detail::MathOperation<detail::MathEngine::AUTO>::clearPaddingBits(
detail::Bits<B>::kDataMask(), out);
}
}
template <std::size_t B, std::size_t N>
template <typename... Args>
void LtHash<B, N>::updateDigest(
Blake2xb& digest,
folly::ByteRange firstRange,
Args&&... moreRanges) {
digest.update(firstRange);
updateDigest(digest, std::forward<Args>(moreRanges)...);
}
template <std::size_t B, std::size_t N>
void LtHash<B, N>::updateDigest(Blake2xb& /* digest */) {}
template <std::size_t B, std::size_t N>
template <typename... Args>
LtHash<B, N>& LtHash<B, N>::addObject(
folly::ByteRange firstRange, Args&&... moreRanges) {
// hash obj and add to elements of checksum
alignas(detail::kCacheLineSize)
std::array<unsigned char, getChecksumSizeBytes()> h;
hashObject(
{h.data(), h.size()}, firstRange, std::forward<Args>(moreRanges)...);
detail::MathOperation<detail::MathEngine::AUTO>::add(
detail::Bits<B>::kDataMask(),
B,
{checksum_.data(), checksum_.length()},
{h.data(), h.size()},
{checksum_.writableData(), checksum_.length()});
return *this;
}
template <std::size_t B, std::size_t N>
template <typename... Args>
LtHash<B, N>& LtHash<B, N>::removeObject(
folly::ByteRange firstRange, Args&&... moreRanges) {
// hash obj and subtract from elements of checksum
alignas(detail::kCacheLineSize)
std::array<unsigned char, getChecksumSizeBytes()> h;
hashObject(
{h.data(), h.size()}, firstRange, std::forward<Args>(moreRanges)...);
detail::MathOperation<detail::MathEngine::AUTO>::sub(
detail::Bits<B>::kDataMask(),
B,
{checksum_.data(), checksum_.length()},
{h.data(), h.size()},
{checksum_.writableData(), checksum_.length()});
return *this;
}
/* static */
template <std::size_t B, std::size_t N>
constexpr size_t LtHash<B, N>::getChecksumSizeBytes() {
return detail::getChecksumSizeBytes<B, N>();
}
/* static */
template <std::size_t B, std::size_t N>
constexpr size_t LtHash<B, N>::getElementSizeInBits() {
return B;
}
/* static */
template <std::size_t B, std::size_t N>
constexpr size_t LtHash<B, N>::getElementsPerUint64() {
return detail::getElementsPerUint64<B>();
}
/* static */
template <std::size_t B, std::size_t N>
constexpr size_t LtHash<B, N>::getElementCount() {
return N;
}
/* static */
template <std::size_t B, std::size_t N>
constexpr bool LtHash<B, N>::hasPaddingBits() {
return detail::Bits<B>::needsPadding();
}
template <std::size_t B, std::size_t N>
std::unique_ptr<folly::IOBuf>
LtHash<B, N>::getChecksum() const {
auto result = std::make_unique<folly::IOBuf>(
detail::allocateCacheAlignedIOBuf(checksum_.length()));
result->append(checksum_.length());
std::memcpy(result->writableData(), checksum_.data(), checksum_.length());
return result;
}
} // namespace crypto
} // namespace folly
/*
* Copyright 2017-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/experimental/crypto/LtHash.h>
#include <folly/CpuId.h>
#ifdef __SSE2__
#include <emmintrin.h>
#endif
#ifdef __AVX2__
#include <immintrin.h>
#endif
#include <folly/Memory.h>
namespace folly {
namespace crypto {
namespace detail {
folly::IOBuf allocateCacheAlignedIOBuf(size_t size) {
void* ptr = folly::aligned_malloc(size, kCacheLineSize);
if (ptr == nullptr) {
throw std::bad_alloc();
}
return folly::IOBuf(
folly::IOBuf::TAKE_OWNERSHIP,
ptr,
static_cast<uint64_t>(size), // capacity
0ULL, // initial size
[](void* addr, void* /* userData*/) { folly::aligned_free(addr); });
}
std::unique_ptr<folly::IOBuf> allocateCacheAlignedIOBufUnique(size_t size) {
return std::make_unique<folly::IOBuf>(allocateCacheAlignedIOBuf(size));
}
bool isCacheAlignedAddress(const void* addr) {
size_t addrValue = reinterpret_cast<size_t>(addr);
return (addrValue & (kCacheLineSize - 1)) == 0;
}
// static
template <>
bool MathOperation<MathEngine::SIMPLE>::isAvailable() {
return true;
}
// static
template <>
bool MathOperation<MathEngine::SSE2>::isAvailable() {
static const bool kIsAvailable =
CpuId().sse2() && MathOperation<MathEngine::SSE2>::isImplemented();
return kIsAvailable;
}
// static
template <>
bool MathOperation<MathEngine::AVX2>::isAvailable() {
static const bool kIsAvailable =
CpuId().avx2() && MathOperation<MathEngine::AVX2>::isImplemented();
return kIsAvailable;
}
// static
template <>
bool MathOperation<MathEngine::AUTO>::isAvailable() {
return true;
}
// static
template <>
bool MathOperation<MathEngine::AUTO>::isImplemented() {
return true;
}
// static
template <>
void MathOperation<MathEngine::AUTO>::add(
uint64_t dataMask,
size_t bitsPerElement,
folly::ByteRange b1,
folly::ByteRange b2,
folly::MutableByteRange out) {
// Note: implementation is a function pointer that is initialized to point
// at the fastest available implementation the first time this function is
// called.
static auto implementation = []() {
if (MathOperation<MathEngine::AVX2>::isAvailable()) {
LOG(INFO) << "Selected AVX2 MathEngine for add() operation";
return MathOperation<MathEngine::AVX2>::add;
} else if (MathOperation<MathEngine::SSE2>::isAvailable()) {
LOG(INFO) << "Selected SSE2 MathEngine for add() operation";
return MathOperation<MathEngine::SSE2>::add;
} else {
LOG(INFO) << "Selected SIMPLE MathEngine for add() operation";
return MathOperation<MathEngine::SIMPLE>::add;
}
}();
implementation(dataMask, bitsPerElement, b1, b2, out);
}
// static
template <>
void MathOperation<MathEngine::AUTO>::sub(
uint64_t dataMask,
size_t bitsPerElement,
folly::ByteRange b1,
folly::ByteRange b2,
folly::MutableByteRange out) {
// Note: implementation is a function pointer that is initialized to point
// at the fastest available implementation the first time this function is
// called.
static auto implementation = []() {
if (MathOperation<MathEngine::AVX2>::isAvailable()) {
LOG(INFO) << "Selected AVX2 MathEngine for sub() operation";
return MathOperation<MathEngine::AVX2>::sub;
} else if (MathOperation<MathEngine::SSE2>::isAvailable()) {
LOG(INFO) << "Selected SSE2 MathEngine for sub() operation";
return MathOperation<MathEngine::SSE2>::sub;
} else {
LOG(INFO) << "Selected SIMPLE MathEngine for sub() operation";
return MathOperation<MathEngine::SIMPLE>::sub;
}
}();
implementation(dataMask, bitsPerElement, b1, b2, out);
}
// static
template <>
void MathOperation<MathEngine::AUTO>::clearPaddingBits(
uint64_t dataMask,
folly::MutableByteRange buf) {
// Note: implementation is a function pointer that is initialized to point
// at the fastest available implementation the first time this function is
// called.
static auto implementation = []() {
if (MathOperation<MathEngine::AVX2>::isAvailable()) {
LOG(INFO) << "Selected AVX2 MathEngine for clearPaddingBits() operation";
return MathOperation<MathEngine::AVX2>::clearPaddingBits;
} else if (MathOperation<MathEngine::SSE2>::isAvailable()) {
LOG(INFO) << "Selected SSE2 MathEngine for clearPaddingBits() operation";
return MathOperation<MathEngine::SSE2>::clearPaddingBits;
} else {
LOG(INFO)
<< "Selected SIMPLE MathEngine for clearPaddingBits() operation";
return MathOperation<MathEngine::SIMPLE>::clearPaddingBits;
}
}();
implementation(dataMask, buf);
}
// static
template <>
bool MathOperation<MathEngine::AUTO>::checkPaddingBits(
uint64_t dataMask,
folly::ByteRange buf) {
// Note: implementation is a function pointer that is initialized to point
// at the fastest available implementation the first time this function is
// called.
static auto implementation = []() {
if (MathOperation<MathEngine::AVX2>::isAvailable()) {
LOG(INFO) << "Selected AVX2 MathEngine for checkPaddingBits() operation";
return MathOperation<MathEngine::AVX2>::checkPaddingBits;
} else if (MathOperation<MathEngine::SSE2>::isAvailable()) {
LOG(INFO) << "Selected SSE2 MathEngine for checkPaddingBits() operation";
return MathOperation<MathEngine::SSE2>::checkPaddingBits;
} else {
LOG(INFO)
<< "Selected SIMPLE MathEngine for checkPaddingBits() operation";
return MathOperation<MathEngine::SIMPLE>::checkPaddingBits;
}
}();
return implementation(dataMask, buf);
}
template struct MathOperation<MathEngine::AUTO>;
} // namespace detail
} // namespace crypto
} // namespace folly
/*
* Copyright 2017-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <cstddef>
#include <memory>
#include <folly/Range.h>
#include <folly/experimental/crypto/Blake2xb.h>
#include <folly/io/IOBuf.h>
namespace folly {
namespace crypto {
namespace detail {
/**
* Allocates an IOBuf of the given size, aligned on a cache line boundary.
* Similar to folly::IOBuf::create(), the returned IOBuf has an initial
* capacity == size and an initial length == 0.
*/
folly::IOBuf allocateCacheAlignedIOBuf(size_t size);
/**
* Similar to allocateCacheAlignedIOBuf(), but returns a unique_ptr to an IOBuf
* instead of an IOBuf.
*/
std::unique_ptr<folly::IOBuf> allocateCacheAlignedIOBufUnique(size_t size);
/**
* Returns true if the given memory address is aligned on a cache line boundary
* and false if it isn't.
*/
bool isCacheAlignedAddress(const void* addr);
} // namespace detail
/**
* Templated homomorphic hash, using LtHash (lattice-based crypto).
* Template parameters: B = element size in bits, N = number of elements.
*
* Current constraints (checked at compile time with static asserts):
* (1) B must be 16, 20 or 32.
* (2) N must be > 999.
* (3) when B is 16, N must be divisible by 32.
* (4) when B is 20, N must be divisible by 24.
* (5) when B is 32, N must be divisible by 16.
*/
template <std::size_t B, std::size_t N>
class LtHash {
public:
explicit LtHash(const folly::IOBuf& initialChecksum = {});
/**
* Like the above constructor but takes ownership of the checksum buffer,
* avoiding a copy if these conditions about the input buffer are met:
* - initialChecksum->isChained() is false
* - initialChecksum->isShared() is false
* - detail::isCacheAlignedAddress(initialChecksum.data()) is true
*
* If you want to take advantage of this and need to make sure your IOBuf
* address is aligned on a cache line boundary, you can use the
* function detail::allocateCacheAlignedIOBufUnique() to do it.
*/
explicit LtHash(std::unique_ptr<folly::IOBuf> initialChecksum);
// Note: we explicitly implement copy constructor and copy assignment
// operator to make sure the checksum_ IOBuf is deep-copied.
LtHash(const LtHash<B, N>& that);
LtHash<B, N>& operator=(const LtHash<B, N>& that);
LtHash(LtHash<B, N>&& that) noexcept = default;
LtHash<B, N>& operator=(LtHash<B, N>&& that) noexcept = default;
~LtHash() = default;
/**
* Resets the checksum in this LtHash. This puts the hash into the same
* state as if it was just constructed with the zero-argument constructor.
*/
void reset();
/**
* IMPORTANT: Unlike regular hash, the incremental hash functions operate on
* individual objects, not a stream of data. For example, the following
* example codes will lead to different checksum values.
* (1) addObject("Hello"); addObject(" World");
* (2) addObject("Hello World");
* because addObject() calculates hashes for the two words separately, and
* aggregate them to update checksum.
*
* addObject() is commutative. LtHash generates the same checksum over a
* given set of objects regardless of the order they were added.
* Example: H(a + b + c) = H(b + c + a)
*
* addObject() can be called with multiple ByteRange parameters, in which
* case it will behave as if it was called with a single ByteRange which
* contained the concatenation of all the input ByteRanges. This allows
* adding an object whose hash is computed from several non-contiguous
* ranges of data, without having to copy the data to a contiguous
* piece of memory.
*
* Example: addObject(r1, r2, r3) is equivalent to
* addObject(r4) where r4 contains the concatenation of r1 + r2 + r3.
*/
template <typename... Args>
LtHash<B, N>& addObject(folly::ByteRange firstRange, Args&&... moreRanges);
/**
* removeObject() is the inverse function of addObject(). Note that it does
* NOT check whether the object has been actually added to LtHash. The caller
* should ensure that the object is valid.
*
* Example: H(a - a + b - b + c - c) = H(a + b + c - a - b - c) = H()
*
* Similar to addObject(), removeObject() can be called with more than one
* ByteRange parameter.
*/
template <typename... Args>
LtHash<B, N>& removeObject(folly::ByteRange firstRange, Args&&... moreRanges);
/**
* Because the addObject() operation in LtHash is commutative and transitive,
* it's possible to break down a large LtHash computation (i.e. adding 100k
* objects) into several parallel steps each of which computes a LtHash of a
* subset of the objects, and then add the LtHash objects together.
* Pseudocode:
*
* std::vector<std::string> objects = ...;
* Future<LtHash<20, 1008>> h1 = computeInBackgroundThread(
* &objects[0], &objects[10000]);
* Future<LtHash<20, 1008>> h2 = computeInBackgroundThread(
* &objects[10001], &objects[20000]);
* LtHash<20, 1008> result = h1.get() + h2.get();
*/
LtHash<B, N>& operator+=(const LtHash<B, N>& rhs);
friend LtHash<B, N> operator+(
const LtHash<B, N>& lhs,
const LtHash<B, N>& rhs) {
LtHash<B, N> result = lhs;
result += rhs;
return result;
}
friend LtHash<B, N> operator+(LtHash<B, N>&& lhs, const LtHash<B, N>& rhs) {
LtHash<B, N> result = std::move(lhs);
result += rhs;
return result;
}
friend LtHash<B, N> operator+(const LtHash<B, N>& lhs, LtHash<B, N>&& rhs) {
// addition is commutative so we can just swap the two arguments
return std::move(rhs) + lhs;
}
friend LtHash<B, N> operator+(LtHash<B, N>&& lhs, LtHash<B, N>&& rhs) {
LtHash<B, N> result = std::move(lhs);
result += rhs;
return result;
}
/**
* The subtraction operator is provided for symmetry, but I'm not sure if
* anyone will ever actually use it outside of tests.
*/
LtHash<B, N>& operator-=(const LtHash<B, N>& rhs);
friend LtHash<B, N> operator-(
const LtHash<B, N>& lhs,
const LtHash<B, N>& rhs) {
LtHash<B, N> result = lhs;
result -= rhs;
return result;
}
friend LtHash<B, N> operator-(LtHash<B, N>&& lhs, const LtHash<B, N>& rhs) {
LtHash<B, N> result = std::move(lhs);
result -= rhs;
return result;
}
/**
* Equality comparison operator, implemented in a data-independent way to
* guard against timing attacks. Always use this to check if two LtHash
* values are equal instead of manually comparing checksum buffers.
*/
bool operator==(const LtHash<B, N>& that) const;
/**
* Equality comparison operator for checksum in ByteRange, implemented in a
* data-independent way to guard against timing attacks.
*/
bool checksumEquals(folly::ByteRange otherChecksum) const;
/**
* Inequality comparison operator.
*/
bool operator!=(const LtHash<B, N>& that) const;
/**
* Sets the intial checksum value to use for processing objects in the
* xxxObject() calls.
*/
void setChecksum(const folly::IOBuf& checksum);
/**
* Like the above method but takes ownership of the checksum buffer,
* avoiding a copy if these conditions about the input buffer are met:
* - checksum->isChained() is false
* - checksum->isShared() is false
* - detail::isCacheAlignedAddress(checksum.data()) is true
*
* If you want to take advantage of this and need to make sure your IOBuf
* address is aligned on a cache line boundary, you can use the
* function detail::allocateCacheAlignedIOBufUnique() to do it.
*/
void setChecksum(std::unique_ptr<folly::IOBuf> checksum);
/**
* Returns the total length of the checksum (element_count * element_length)
*/
static constexpr size_t getChecksumSizeBytes();
/**
* Returns the template parameter B.
*/
static constexpr size_t getElementSizeInBits();
/**
* Returns the number of elements that get packed into a single uint64_t.
*/
static constexpr size_t getElementsPerUint64();
/**
* Returns the template parameter N.
*/
static constexpr size_t getElementCount();
/**
* Retruns true if the internal checksum uses padding bits between elements.
*/
static constexpr bool hasPaddingBits();
/**
* Returns a copy of the current checksum value
*/
std::unique_ptr<folly::IOBuf> getChecksum() const;
private:
template <typename... Args>
void hashObject(
folly::MutableByteRange out,
folly::ByteRange firstRange,
Args&&... moreRanges);
template <typename... Args>
void
updateDigest(Blake2xb& digest, folly::ByteRange range, Args&&... moreRanges);
void updateDigest(Blake2xb& digest);
// current checksum
folly::IOBuf checksum_;
};
} // namespace crypto
} // namespace folly
#include <folly/experimental/crypto/LtHash-inl.h>
namespace folly {
namespace crypto {
// This is the fastest and smallest specialization and should be
// preferred in most cases. It provides over 200 bits of security
// which should be good enough for most cases.
using LtHash16_1024 = LtHash<16, 1024>;
// These specializations are available to users who want a higher
// level of cryptographic security. They are slower and larger than
// the one above.
using LtHash20_1008 = LtHash<20, 1008>;
using LtHash32_1024 = LtHash<32, 1024>;
} // namespace crypto
} // namespace folly
/*
* Copyright 2017-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <folly/Range.h>
namespace folly {
namespace crypto {
namespace detail {
// As of 2019, most (or all?) modern Intel CPUs have 64-byte L1 cache lines,
// and aligning data buffers on cache line boundaries on such CPUs
// noticeably benefits performance (up to 10% difference).
//
// If you change this, code that depends on it in MathOperation_*.cpp may
// break and could need fixing.
constexpr size_t kCacheLineSize = 64;
// Invariants about kCacheLineSize that other logic depends on: it must be
// a power of 2 and cannot be zero.
static_assert(kCacheLineSize > 0, "kCacheLineSize cannot be 0");
static_assert(
(kCacheLineSize & (kCacheLineSize - 1)) == 0,
"kCacheLineSize must be a power of 2");
/**
* Defines available math engines that we can use to perform element-wise
* modular addition or subtraction of element vectors.
* - AUTO: pick the best available, from best to worst: AVX2, SSE2, SIMPLE
* - SIMPLE: perform addition/subtraction using uint64_t values
* - SSE2: perform addition/subtraction using 128-bit __m128i values.
* Intel only, requires SSE2 instruction support.
* - AVX2: perform addition/subtraction using 256-bit __m256i values.
* Intel only, requires AVX2 instruction support.
*/
enum class MathEngine { AUTO, SIMPLE, SSE2, AVX2 };
/**
* This actually implements the bulk addition/subtraction operations.
*/
template <MathEngine E>
struct MathOperation {
/**
* Returns true if the math engine E is supported by the CPU and OS and is
* implemented.
*/
static bool isAvailable();
/**
* Returns true if the math engine E is implemented.
*/
static bool isImplemented();
/**
* Performs element-wise modular addition of 2 vectors of elements packed
* into the buffers b1 and b2. Writes the output into the buffer out. The
* output buffer may be the same as one of the input buffers. The dataMask
* parameter should be Bits<B>::kDataMask() where B is the element size
* in bits.
*/
static void add(
uint64_t dataMask,
size_t bitsPerElement,
ByteRange b1,
ByteRange b2,
MutableByteRange out);
/**
* Performs element-wise modular subtraction of 2 groups of elements packed
* into the buffers b1 and b2. Note that (a - b) % M == (a + (M - b)) % M,
* which is how we actually implement it to avoid underflow issues. The
* dataMask parameter should be Bits<B>::kDataMask() where B is the element
* size in bits.
*/
static void sub(
uint64_t dataMask,
size_t bitsPerElement,
ByteRange b1,
ByteRange b2,
MutableByteRange out);
/**
* Clears the padding bits of the given buffer according to the given
* data mask: for each uint64_t in the input buffer, all 0 bits in the
* data mask are cleared, and all 1 bits in the data mask are preserved.
*/
static void clearPaddingBits(uint64_t dataMask, MutableByteRange buf);
/**
* Returns true if the given checksum buffer contains 0 bits at the padding
* bit positions, according to the given data mask.
*/
static bool checkPaddingBits(uint64_t dataMask, ByteRange buf);
};
// These forward declarations of explicit template instantiations seem to be
// required to get things to compile. I tried to get things to work without it,
// but the compiler complained when I had any AVX2 types in this header, so I
// think they need to be hidden in the .cpp file for some reason.
#define FORWARD_DECLARE_EXTERN_TEMPLATE(E) \
template <> \
bool MathOperation<E>::isAvailable(); \
template <> \
bool MathOperation<E>::isImplemented(); \
template <> \
void MathOperation<E>::add( \
uint64_t dataMask, \
size_t bitsPerElement, \
ByteRange b1, \
ByteRange b2, \
MutableByteRange out); \
template <> \
void MathOperation<E>::sub( \
uint64_t dataMask, \
size_t bitsPerElement, \
ByteRange b1, \
ByteRange b2, \
MutableByteRange out); \
template <> \
void MathOperation<E>::clearPaddingBits( \
uint64_t dataMask, MutableByteRange buf); \
template <> \
bool MathOperation<E>::checkPaddingBits(uint64_t dataMask, ByteRange buf); \
extern template struct MathOperation<E>
FORWARD_DECLARE_EXTERN_TEMPLATE(MathEngine::AUTO);
FORWARD_DECLARE_EXTERN_TEMPLATE(MathEngine::SIMPLE);
FORWARD_DECLARE_EXTERN_TEMPLATE(MathEngine::SSE2);
FORWARD_DECLARE_EXTERN_TEMPLATE(MathEngine::AVX2);
#undef FORWARD_DECLARE_EXTERN_TEMPLATE
} // namespace detail
} // namespace crypto
} // namespace folly
/*
* Copyright 2017-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Implementation of the MathOperation<MathEngine::AVX2> template
// specializations.
#include <folly/experimental/crypto/detail/LtHashInternal.h>
#ifdef __AVX2__
#include <immintrin.h>
#include <sodium.h>
#include <folly/lang/Bits.h>
#endif // __AVX2__
#include <folly/Memory.h>
namespace folly {
namespace crypto {
namespace detail {
#ifdef __AVX2__
// static
template <>
bool MathOperation<MathEngine::AVX2>::isImplemented() {
return true;
}
// static
template <>
void MathOperation<MathEngine::AVX2>::add(
uint64_t dataMask,
size_t bitsPerElement,
ByteRange b1,
ByteRange b2,
MutableByteRange out) {
DCHECK_EQ(b1.size(), b2.size());
DCHECK_EQ(b1.size(), out.size());
DCHECK_EQ(0, b1.size() % kCacheLineSize);
static_assert(
kCacheLineSize % sizeof(__m256i) == 0,
"kCacheLineSize must be a multiple of sizeof(__m256i)");
static constexpr size_t kValsPerCacheLine = kCacheLineSize / sizeof(__m256i);
static_assert(
kValsPerCacheLine > 0, "kCacheLineSize must be >= sizeof(__m256i)");
// gcc issues 'ignoring attributes on template argument' warning if
// __m256i is used below, so have to type explicitly
alignas(kCacheLineSize) std::array<
long long __attribute__((__vector_size__(sizeof(__m256i)))),
kValsPerCacheLine>
results;
// Note: AVX2 is Intel x86_64 only which is little-endian, so we don't need
// the Endian::little() conversions when loading or storing data.
if (bitsPerElement == 16 || bitsPerElement == 32) {
for (size_t pos = 0; pos < b1.size(); pos += kCacheLineSize) {
const __m256i* v1p = reinterpret_cast<const __m256i*>(b1.data() + pos);
const __m256i* v2p = reinterpret_cast<const __m256i*>(b2.data() + pos);
for (size_t i = 0; i < kValsPerCacheLine; ++i) {
__m256i v1 = _mm256_load_si256(v1p + i);
__m256i v2 = _mm256_load_si256(v2p + i);
if (bitsPerElement == 16) {
results[i] = _mm256_add_epi16(v1, v2);
} else { // bitsPerElement == 32
results[i] = _mm256_add_epi32(v1, v2);
}
}
std::memcpy(out.data() + pos, results.data(), sizeof(results));
}
} else {
__m256i mask = _mm256_set1_epi64x(dataMask);
for (size_t pos = 0; pos < b1.size(); pos += kCacheLineSize) {
const __m256i* v1p = reinterpret_cast<const __m256i*>(b1.data() + pos);
const __m256i* v2p = reinterpret_cast<const __m256i*>(b2.data() + pos);
for (size_t i = 0; i < kValsPerCacheLine; ++i) {
__m256i v1 = _mm256_load_si256(v1p + i);
__m256i v2 = _mm256_load_si256(v2p + i);
results[i] = _mm256_and_si256(_mm256_add_epi64(v1, v2), mask);
}
std::memcpy(out.data() + pos, results.data(), sizeof(results));
}
}
}
// static
template <>
void MathOperation<MathEngine::AVX2>::sub(
uint64_t dataMask,
size_t bitsPerElement,
ByteRange b1,
ByteRange b2,
MutableByteRange out) {
DCHECK_EQ(b1.size(), b2.size());
DCHECK_EQ(b1.size(), out.size());
DCHECK_EQ(0, b1.size() % kCacheLineSize);
static_assert(
kCacheLineSize % sizeof(__m256i) == 0,
"kCacheLineSize must be a multiple of sizeof(__m256i)");
static constexpr size_t kValsPerCacheLine = kCacheLineSize / sizeof(__m256i);
static_assert(
kValsPerCacheLine > 0, "kCacheLineSize must be >= sizeof(__m256i)");
// gcc issues 'ignoring attributes on template argument' warning if
// __m256i is used below, so have to type explicitly
alignas(kCacheLineSize) std::array<
long long __attribute__((__vector_size__(sizeof(__m256i)))),
kValsPerCacheLine>
results;
// Note: AVX2 is Intel x86_64 only which is little-endian, so we don't need
// the Endian::little() conversions when loading or storing data.
if (bitsPerElement == 16 || bitsPerElement == 32) {
for (size_t pos = 0; pos < b1.size(); pos += kCacheLineSize) {
const __m256i* v1p = reinterpret_cast<const __m256i*>(b1.data() + pos);
const __m256i* v2p = reinterpret_cast<const __m256i*>(b2.data() + pos);
for (size_t i = 0; i < kValsPerCacheLine; ++i) {
__m256i v1 = _mm256_load_si256(v1p + i);
__m256i v2 = _mm256_load_si256(v2p + i);
if (bitsPerElement == 16) {
results[i] = _mm256_sub_epi16(v1, v2);
} else { // bitsPerElement == 32
results[i] = _mm256_sub_epi32(v1, v2);
}
}
std::memcpy(out.data() + pos, results.data(), sizeof(results));
}
} else {
__m256i mask = _mm256_set1_epi64x(dataMask);
__m256i paddingMask = _mm256_set1_epi64x(~dataMask);
for (size_t pos = 0; pos < b1.size(); pos += kCacheLineSize) {
const __m256i* v1p = reinterpret_cast<const __m256i*>(b1.data() + pos);
const __m256i* v2p = reinterpret_cast<const __m256i*>(b2.data() + pos);
for (size_t i = 0; i < kValsPerCacheLine; ++i) {
__m256i v1 = _mm256_load_si256(v1p + i);
__m256i v2 = _mm256_load_si256(v2p + i);
__m256i negV2 =
_mm256_and_si256(_mm256_sub_epi64(paddingMask, v2), mask);
results[i] = _mm256_and_si256(_mm256_add_epi64(v1, negV2), mask);
}
std::memcpy(out.data() + pos, results.data(), sizeof(results));
}
}
}
template <>
void MathOperation<MathEngine::AVX2>::clearPaddingBits(
uint64_t dataMask,
MutableByteRange buf) {
if (dataMask == 0xffffffffffffffffULL) {
return;
}
DCHECK_EQ(0, buf.size() % kCacheLineSize);
static_assert(
kCacheLineSize % sizeof(__m256i) == 0,
"kCacheLineSize must be a multiple of sizeof(__m256i)");
static constexpr size_t kValsPerCacheLine = kCacheLineSize / sizeof(__m256i);
static_assert(
kValsPerCacheLine > 0, "kCacheLineSize must be >= sizeof(__m256i)");
// gcc issues 'ignoring attributes on template argument' warning if
// __m256i is used below, so have to type explicitly
alignas(kCacheLineSize) std::array<
long long __attribute__((__vector_size__(sizeof(__m256i)))),
kValsPerCacheLine>
results;
__m256i mask = _mm256_set1_epi64x(dataMask);
for (size_t pos = 0; pos < buf.size(); pos += kCacheLineSize) {
const __m256i* p = reinterpret_cast<const __m256i*>(buf.data() + pos);
for (size_t i = 0; i < kValsPerCacheLine; ++i) {
results[i] = _mm256_and_si256(_mm256_load_si256(p + i), mask);
}
std::memcpy(buf.data() + pos, results.data(), sizeof(results));
}
}
template <>
bool MathOperation<MathEngine::AVX2>::checkPaddingBits(
uint64_t dataMask,
ByteRange buf) {
if (dataMask == 0xffffffffffffffffULL) {
return true;
}
DCHECK_EQ(0, buf.size() % sizeof(__m256i));
__m256i paddingMask = _mm256_set1_epi64x(~dataMask);
static const __m256i kZero = _mm256_setzero_si256();
for (size_t pos = 0; pos < buf.size(); pos += sizeof(__m256i)) {
__m256i val =
_mm256_load_si256(reinterpret_cast<const __m256i*>(buf.data() + pos));
__m256i paddingBits = _mm256_and_si256(val, paddingMask);
if (sodium_memcmp(&paddingBits, &kZero, sizeof(kZero)) != 0) {
return false;
}
}
return true;
}
#else // !__AVX2__
// static
template <>
bool MathOperation<MathEngine::AVX2>::isImplemented() {
return false;
}
// static
template <>
void MathOperation<MathEngine::AVX2>::add(
uint64_t /* dataMask */,
size_t bitsPerElement,
ByteRange /* b1 */,
ByteRange /* b2 */,
MutableByteRange /* out */) {
if (bitsPerElement != 0) { // hack to defeat [[noreturn]] compiler warning
LOG(FATAL) << "Unimplemented function MathOperation<MathEngine::AVX2>::"
<< "add() called";
}
}
// static
template <>
void MathOperation<MathEngine::AVX2>::sub(
uint64_t /* dataMask */,
size_t bitsPerElement,
ByteRange /* b1 */,
ByteRange /* b2 */,
MutableByteRange /* out */) {
if (bitsPerElement != 0) { // hack to defeat [[noreturn]] compiler warning
LOG(FATAL) << "Unimplemented function MathOperation<MathEngine::AVX2>::"
<< "sub() called";
}
}
template <>
void MathOperation<MathEngine::AVX2>::clearPaddingBits(
uint64_t /* dataMask */,
MutableByteRange buf) {
if (buf.data() != nullptr) { // hack to defeat [[noreturn]] compiler warning
LOG(FATAL) << "Unimplemented function MathOperation<MathEngine::AVX2>::"
<< "clearPaddingBits() called";
}
}
template <>
bool MathOperation<MathEngine::AVX2>::checkPaddingBits(
uint64_t /* dataMask */,
ByteRange buf) {
if (buf.data() != nullptr) { // hack to defeat [[noreturn]] compiler warning
LOG(FATAL) << "Unimplemented function MathOperation<MathEngine::AVX2>::"
<< "checkPaddingBits() called";
}
return false;
}
#endif // __AVX2__
template struct MathOperation<MathEngine::AVX2>;
} // namespace detail
} // namespace crypto
} // namespace folly
/*
* Copyright 2017-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Implementation of the MathOperation<MathEngine::SSE2> template
// specializations.
#include <folly/experimental/crypto/detail/LtHashInternal.h>
#ifdef __SSE2__
#include <emmintrin.h>
#include <sodium.h>
#include <folly/lang/Bits.h>
#endif // __SSE2__
#include <folly/Memory.h>
namespace folly {
namespace crypto {
namespace detail {
#ifdef __SSE2__
// static
template <>
bool MathOperation<MathEngine::SSE2>::isImplemented() {
return true;
}
// static
template <>
void MathOperation<MathEngine::SSE2>::add(
uint64_t dataMask,
size_t bitsPerElement,
ByteRange b1,
ByteRange b2,
MutableByteRange out) {
DCHECK_EQ(b1.size(), b2.size());
DCHECK_EQ(b1.size(), out.size());
DCHECK_EQ(0, b1.size() % kCacheLineSize);
static_assert(
kCacheLineSize % sizeof(__m128i) == 0,
"kCacheLineSize must be a multiple of sizeof(__m128i)");
static constexpr size_t kValsPerCacheLine = kCacheLineSize / sizeof(__m128i);
static_assert(
kValsPerCacheLine > 0, "kCacheLineSize must be >= sizeof(__m128i)");
// gcc issues 'ignoring attributes on template argument' warning if
// __m128i is used below, so have to type explicitly
alignas(kCacheLineSize) std::array<
long long __attribute__((__vector_size__(sizeof(__m128i)))),
kValsPerCacheLine>
results;
// Note: SSE2 is Intel x86(_64) only which is little-endian, so we don't need
// the Endian::little() conversions when loading or storing data.
if (bitsPerElement == 16 || bitsPerElement == 32) {
for (size_t pos = 0; pos < b1.size(); pos += kCacheLineSize) {
const __m128i* v1p = reinterpret_cast<const __m128i*>(b1.data() + pos);
const __m128i* v2p = reinterpret_cast<const __m128i*>(b2.data() + pos);
for (size_t i = 0; i < kValsPerCacheLine; ++i) {
__m128i v1 = _mm_load_si128(v1p + i);
__m128i v2 = _mm_load_si128(v2p + i);
if (bitsPerElement == 16) {
results[i] = _mm_add_epi16(v1, v2);
} else { // bitsPerElement == 32
results[i] = _mm_add_epi32(v1, v2);
}
}
std::memcpy(out.data() + pos, results.data(), sizeof(results));
}
} else {
__m128i mask = _mm_set_epi64x(dataMask, dataMask);
for (size_t pos = 0; pos < b1.size(); pos += kCacheLineSize) {
const __m128i* v1p = reinterpret_cast<const __m128i*>(b1.data() + pos);
const __m128i* v2p = reinterpret_cast<const __m128i*>(b2.data() + pos);
for (size_t i = 0; i < kValsPerCacheLine; ++i) {
__m128i v1 = _mm_load_si128(v1p + i);
__m128i v2 = _mm_load_si128(v2p + i);
results[i] = _mm_and_si128(_mm_add_epi64(v1, v2), mask);
}
std::memcpy(out.data() + pos, results.data(), sizeof(results));
}
}
}
// static
template <>
void MathOperation<MathEngine::SSE2>::sub(
uint64_t dataMask,
size_t bitsPerElement,
ByteRange b1,
ByteRange b2,
MutableByteRange out) {
DCHECK_EQ(b1.size(), b2.size());
DCHECK_EQ(b1.size(), out.size());
DCHECK_EQ(0, b1.size() % kCacheLineSize);
static_assert(
kCacheLineSize % sizeof(__m128i) == 0,
"kCacheLineSize must be a multiple of sizeof(__m128i)");
static constexpr size_t kValsPerCacheLine = kCacheLineSize / sizeof(__m128i);
static_assert(
kValsPerCacheLine > 0, "kCacheLineSize must be >= sizeof(__m128i)");
// gcc issues 'ignoring attributes on template argument' warning if
// __m128i is used below, so have to type explicitly
alignas(kCacheLineSize) std::array<
long long __attribute__((__vector_size__(sizeof(__m128i)))),
kValsPerCacheLine>
results;
// Note: SSE2 is Intel x86(_64) only which is little-endian, so we don't need
// the Endian::little() conversions when loading or storing data.
if (bitsPerElement == 16 || bitsPerElement == 32) {
for (size_t pos = 0; pos < b1.size(); pos += kCacheLineSize) {
const __m128i* v1p = reinterpret_cast<const __m128i*>(b1.data() + pos);
const __m128i* v2p = reinterpret_cast<const __m128i*>(b2.data() + pos);
for (size_t i = 0; i < kValsPerCacheLine; ++i) {
__m128i v1 = _mm_load_si128(v1p + i);
__m128i v2 = _mm_load_si128(v2p + i);
if (bitsPerElement == 16) {
results[i] = _mm_sub_epi16(v1, v2);
} else { // bitsPerElement == 32
results[i] = _mm_sub_epi32(v1, v2);
}
}
std::memcpy(out.data() + pos, results.data(), sizeof(results));
}
} else {
__m128i mask = _mm_set_epi64x(dataMask, dataMask);
__m128i paddingMask = _mm_set_epi64x(~dataMask, ~dataMask);
for (size_t pos = 0; pos < b1.size(); pos += kCacheLineSize) {
const __m128i* v1p = reinterpret_cast<const __m128i*>(b1.data() + pos);
const __m128i* v2p = reinterpret_cast<const __m128i*>(b2.data() + pos);
for (size_t i = 0; i < kValsPerCacheLine; ++i) {
__m128i v1 = _mm_load_si128(v1p + i);
__m128i v2 = _mm_load_si128(v2p + i);
__m128i negV2 = _mm_and_si128(_mm_sub_epi64(paddingMask, v2), mask);
results[i] = _mm_and_si128(_mm_add_epi64(v1, negV2), mask);
}
std::memcpy(out.data() + pos, results.data(), sizeof(results));
}
}
}
template <>
void MathOperation<MathEngine::SSE2>::clearPaddingBits(
uint64_t dataMask,
MutableByteRange buf) {
if (dataMask == 0xffffffffffffffffULL) {
return;
}
DCHECK_EQ(0, buf.size() % kCacheLineSize);
static_assert(
kCacheLineSize % sizeof(__m128i) == 0,
"kCacheLineSize must be a multiple of sizeof(__m128i)");
static constexpr size_t kValsPerCacheLine = kCacheLineSize / sizeof(__m128i);
static_assert(
kValsPerCacheLine > 0, "kCacheLineSize must be >= sizeof(__m128i)");
// gcc issues 'ignoring attributes on template argument' warning if
// __m128i is used below, so have to type explicitly
alignas(kCacheLineSize) std::array<
long long __attribute__((__vector_size__(sizeof(__m128i)))),
kValsPerCacheLine>
results;
__m128i mask = _mm_set_epi64x(dataMask, dataMask);
for (size_t pos = 0; pos < buf.size(); pos += kCacheLineSize) {
const __m128i* p = reinterpret_cast<const __m128i*>(buf.data() + pos);
for (size_t i = 0; i < kValsPerCacheLine; ++i) {
results[i] = _mm_and_si128(_mm_load_si128(p + i), mask);
}
std::memcpy(buf.data() + pos, results.data(), sizeof(results));
}
}
template <>
bool MathOperation<MathEngine::SSE2>::checkPaddingBits(
uint64_t dataMask,
ByteRange buf) {
if (dataMask == 0xffffffffffffffffULL) {
return true;
}
DCHECK_EQ(0, buf.size() % sizeof(__m128i));
__m128i paddingMask = _mm_set_epi64x(~dataMask, ~dataMask);
static const __m128i kZero = _mm_setzero_si128();
for (size_t pos = 0; pos < buf.size(); pos += sizeof(__m128i)) {
__m128i val =
_mm_load_si128(reinterpret_cast<const __m128i*>(buf.data() + pos));
__m128i paddingBits = _mm_and_si128(val, paddingMask);
if (sodium_memcmp(&paddingBits, &kZero, sizeof(kZero)) != 0) {
return false;
}
}
return true;
}
#else // !__SSE2__
// static
template <>
bool MathOperation<MathEngine::SSE2>::isImplemented() {
return false;
}
// static
template <>
void MathOperation<MathEngine::SSE2>::add(
uint64_t /* dataMask */,
size_t bitsPerElement,
ByteRange /* b1 */,
ByteRange /* b2 */,
MutableByteRange /* out */) {
if (bitsPerElement != 0) { // hack to defeat [[noreturn]] compiler warning
LOG(FATAL) << "Unimplemented function MathOperation<MathEngine::SSE2>::"
<< "add() called";
}
}
// static
template <>
void MathOperation<MathEngine::SSE2>::sub(
uint64_t /* dataMask */,
size_t bitsPerElement,
ByteRange /* b1 */,
ByteRange /* b2 */,
MutableByteRange /* out */) {
if (bitsPerElement != 0) { // hack to defeat [[noreturn]] compiler warning
LOG(FATAL) << "Unimplemented function MathOperation<MathEngine::SSE2>::"
<< "sub() called";
}
}
template <>
void MathOperation<MathEngine::SSE2>::clearPaddingBits(
uint64_t /* dataMask */,
MutableByteRange buf) {
if (buf.data() != nullptr) { // hack to defeat [[noreturn]] compiler warning
LOG(FATAL) << "Unimplemented function MathOperation<MathEngine::SSE2>::"
<< "clearPaddingBits() called";
}
return; // not reached
}
template <>
bool MathOperation<MathEngine::SSE2>::checkPaddingBits(
uint64_t /* dataMask */,
ByteRange buf) {
if (buf.data() != nullptr) { // hack to defeat [[noreturn]] compiler warning
LOG(FATAL) << "Unimplemented function MathOperation<MathEngine::SSE2>::"
<< "checkPaddingBits() called";
}
return false;
}
#endif // __SSE2__
template struct MathOperation<MathEngine::SSE2>;
} // namespace detail
} // namespace crypto
} // namespace folly
/*
* Copyright 2017-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Implementation of the MathOperation<MathEngine::SIMPLE> template
// specializations.
#include <folly/experimental/crypto/detail/LtHashInternal.h>
#include <folly/Memory.h>
#include <folly/lang/Bits.h>
namespace folly {
namespace crypto {
namespace detail {
// static
template <>
bool MathOperation<MathEngine::SIMPLE>::isImplemented() {
return true;
}
// static
template <>
void MathOperation<MathEngine::SIMPLE>::add(
uint64_t dataMask,
size_t bitsPerElement,
ByteRange b1,
ByteRange b2,
MutableByteRange out) {
DCHECK_EQ(b1.size(), b2.size());
DCHECK_EQ(b1.size(), out.size());
DCHECK_EQ(0, b1.size() % kCacheLineSize);
static_assert(
kCacheLineSize % sizeof(uint64_t) == 0,
"kCacheLineSize must be a multiple of sizeof(uint64_t)");
static constexpr size_t kValsPerCacheLine = kCacheLineSize / sizeof(uint64_t);
static_assert(
kValsPerCacheLine > 0, "kCacheLineSize must be >= sizeof(uint64_t)");
alignas(kCacheLineSize) std::array<uint64_t, kValsPerCacheLine> results;
if (bitsPerElement == 16 || bitsPerElement == 32) {
// When bitsPerElement is 16:
// There are no padding bits, 4x 16-bit values fit exactly into a uint64_t:
// uint64_t U = [ uint16_t W, uint16_t X, uint16_t Y, uint16_t Z ].
// We break them up into A and B groups, with each group containing
// alternating elements, such that A | B = the original number:
// uint64_t A = [ uint16_t W, 0, uint16_t Y, 0 ]
// uint64_t B = [ 0, uint16_t X, 0, uint16_t Z ]
// Then we add the A group and B group independently, and bitwise-OR
// the results.
// When bitsPerElement is 32:
// There are no padding bits, 2x 32-bit values fit exactly into a uint64_t.
// We independently add the high and low halves and then XOR them together.
const uint64_t kMaskA =
bitsPerElement == 16 ? 0xffff0000ffff0000ULL : 0xffffffff00000000ULL;
const uint64_t kMaskB = ~kMaskA;
for (size_t pos = 0; pos < b1.size(); pos += kCacheLineSize) {
const uint64_t* v1p = reinterpret_cast<const uint64_t*>(b1.data() + pos);
const uint64_t* v2p = reinterpret_cast<const uint64_t*>(b2.data() + pos);
for (size_t i = 0; i < kValsPerCacheLine; ++i) {
uint64_t v1 = Endian::little(*(v1p + i));
uint64_t v2 = Endian::little(*(v2p + i));
uint64_t v1a = v1 & kMaskA;
uint64_t v1b = v1 & kMaskB;
uint64_t v2a = v2 & kMaskA;
uint64_t v2b = v2 & kMaskB;
uint64_t v3a = (v1a + v2a) & kMaskA;
uint64_t v3b = (v1b + v2b) & kMaskB;
results[i] = Endian::little(v3a | v3b);
}
std::memcpy(out.data() + pos, results.data(), sizeof(results));
}
} else {
for (size_t pos = 0; pos < b1.size(); pos += kCacheLineSize) {
const uint64_t* v1p = reinterpret_cast<const uint64_t*>(b1.data() + pos);
const uint64_t* v2p = reinterpret_cast<const uint64_t*>(b2.data() + pos);
for (size_t i = 0; i < kValsPerCacheLine; ++i) {
uint64_t v1 = Endian::little(*(v1p + i));
uint64_t v2 = Endian::little(*(v2p + i));
results[i] = Endian::little((v1 + v2) & dataMask);
}
std::memcpy(out.data() + pos, results.data(), sizeof(results));
}
}
}
// static
template <>
void MathOperation<MathEngine::SIMPLE>::sub(
uint64_t dataMask,
size_t bitsPerElement,
ByteRange b1,
ByteRange b2,
MutableByteRange out) {
DCHECK_EQ(b1.size(), b2.size());
DCHECK_EQ(b1.size(), out.size());
DCHECK_EQ(0, b1.size() % kCacheLineSize);
static_assert(
kCacheLineSize % sizeof(uint64_t) == 0,
"kCacheLineSize must be a multiple of sizeof(uint64_t)");
static constexpr size_t kValsPerCacheLine = kCacheLineSize / sizeof(uint64_t);
static_assert(
kValsPerCacheLine > 0, "kCacheLineSize must be >= sizeof(uint64_t)");
alignas(kCacheLineSize) std::array<uint64_t, kValsPerCacheLine> results;
if (bitsPerElement == 16 || bitsPerElement == 32) {
// When bitsPerElement is 16:
// There are no padding bits, 4x 16-bit values fit exactly into a uint64_t:
// uint64_t U = [ uint16_t W, uint16_t X, uint16_t Y, uint16_t Z ].
// We break them up into A and B groups, with each group containing
// alternating elements, such that A | B = the original number:
// uint64_t A = [ uint16_t W, 0, uint16_t Y, 0 ]
// uint64_t B = [ 0, uint16_t X, 0, uint16_t Z ]
// Then we add the A group and B group independently, and bitwise-OR
// the results.
// When bitsPerElement is 32:
// There are no padding bits, 2x 32-bit values fit exactly into a uint64_t.
// We independently add the high and low halves and then XOR them together.
const uint64_t kMaskA =
bitsPerElement == 16 ? 0xffff0000ffff0000ULL : 0xffffffff00000000ULL;
const uint64_t kMaskB = ~kMaskA;
for (size_t pos = 0; pos < b1.size(); pos += kCacheLineSize) {
const uint64_t* v1p = reinterpret_cast<const uint64_t*>(b1.data() + pos);
const uint64_t* v2p = reinterpret_cast<const uint64_t*>(b2.data() + pos);
for (size_t i = 0; i < kValsPerCacheLine; ++i) {
uint64_t v1 = Endian::little(*(v1p + i));
uint64_t v2 = Endian::little(*(v2p + i));
uint64_t v1a = v1 & kMaskA;
uint64_t v1b = v1 & kMaskB;
uint64_t v2a = v2 & kMaskA;
uint64_t v2b = v2 & kMaskB;
uint64_t v3a = (v1a + (kMaskB - v2a)) & kMaskA;
uint64_t v3b = (v1b + (kMaskA - v2b)) & kMaskB;
results[i] = Endian::little(v3a | v3b);
}
std::memcpy(out.data() + pos, results.data(), sizeof(results));
}
} else {
for (size_t pos = 0; pos < b1.size(); pos += kCacheLineSize) {
const uint64_t* v1p = reinterpret_cast<const uint64_t*>(b1.data() + pos);
const uint64_t* v2p = reinterpret_cast<const uint64_t*>(b2.data() + pos);
for (size_t i = 0; i < kValsPerCacheLine; ++i) {
uint64_t v1 = Endian::little(*(v1p + i));
uint64_t v2 = Endian::little(*(v2p + i));
results[i] =
Endian::little((v1 + ((~dataMask - v2) & dataMask)) & dataMask);
}
std::memcpy(out.data() + pos, results.data(), sizeof(results));
}
}
}
template <>
void MathOperation<MathEngine::SIMPLE>::clearPaddingBits(
uint64_t dataMask,
MutableByteRange buf) {
if (dataMask == 0xffffffffffffffffULL) {
return;
}
DCHECK_EQ(0, buf.size() % kCacheLineSize);
static_assert(
kCacheLineSize % sizeof(uint64_t) == 0,
"kCacheLineSize must be a multiple of sizeof(uint64_t)");
static constexpr size_t kValsPerCacheLine = kCacheLineSize / sizeof(uint64_t);
static_assert(
kValsPerCacheLine > 0, "kCacheLineSize must be >= sizeof(uint64_t)");
alignas(kCacheLineSize) std::array<uint64_t, kValsPerCacheLine> results;
for (size_t pos = 0; pos < buf.size(); pos += kCacheLineSize) {
const uint64_t* p = reinterpret_cast<const uint64_t*>(buf.data() + pos);
for (size_t i = 0; i < kValsPerCacheLine; ++i) {
results[i] = Endian::little(Endian::little(*(p + i)) & dataMask);
}
std::memcpy(buf.data() + pos, results.data(), sizeof(results));
}
}
template <>
bool MathOperation<MathEngine::SIMPLE>::checkPaddingBits(
uint64_t dataMask,
ByteRange buf) {
if (dataMask == 0xffffffffffffffffULL) {
return true;
}
DCHECK_EQ(0, buf.size() % sizeof(uint64_t));
for (size_t pos = 0; pos < buf.size(); pos += sizeof(uint64_t)) {
uint64_t val =
Endian::little(*reinterpret_cast<const uint64_t*>(buf.data() + pos));
if ((val & ~dataMask) != 0ULL) {
return false;
}
}
return true;
}
template struct MathOperation<MathEngine::SIMPLE>;
} // namespace detail
} // namespace crypto
} // namespace folly
/*
* Copyright 2017-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/Benchmark.h>
#include <folly/Random.h>
#include <folly/experimental/crypto/LtHash.h>
#include <folly/init/Init.h>
#include <folly/io/IOBuf.h>
#include <glog/logging.h>
#include <sodium.h>
using namespace ::folly::crypto;
namespace {
constexpr size_t kObjectCount = 1000;
constexpr size_t kObjectSize = 150;
std::vector<std::unique_ptr<const folly::IOBuf>> kObjects;
} // namespace
std::unique_ptr<folly::IOBuf> makeRandomData(size_t length) {
auto data = std::make_unique<folly::IOBuf>(
folly::crypto::detail::allocateCacheAlignedIOBuf(length));
data->append(length);
randombytes_buf(data->writableData(), data->length());
return data;
}
template <std::size_t B, std::size_t N>
void runBenchmark(size_t n) {
LtHash<B, N> ltHash;
for (size_t i = 0; i < static_cast<size_t>(n); ++i) {
const folly::IOBuf& obj = *(kObjects[i % kObjects.size()]);
ltHash.addObject({obj.data(), obj.length()});
}
}
BENCHMARK(single_blake2b, n) {
std::array<unsigned char, crypto_generichash_blake2b_BYTES_MAX> result;
for (size_t i = 0; i < static_cast<size_t>(n); ++i) {
const folly::IOBuf& obj = *(kObjects[i % kObjects.size()]);
int res = crypto_generichash_blake2b(
result.data(), sizeof(result), obj.data(), obj.length(), nullptr, 0);
if (res != 0) {
throw std::runtime_error("blake2b hash failed");
}
}
}
BENCHMARK_RELATIVE(LtHash_element_count_1024_length_16, n) {
runBenchmark<16, 1024>(static_cast<size_t>(n));
}
BENCHMARK_RELATIVE(LtHash_element_count_1008_length_20, n) {
runBenchmark<20, 1008>(static_cast<size_t>(n));
}
BENCHMARK_RELATIVE(LtHash_element_count_1024_length_32, n) {
runBenchmark<32, 1024>(static_cast<size_t>(n));
}
BENCHMARK_RELATIVE(LtHash_element_count_2048_length_32, n) {
runBenchmark<32, 2048>(static_cast<size_t>(n));
}
BENCHMARK(calculateChecksumFor100KObjects_B20_N1008) {
LtHash<20, 1008> ltHash;
for (auto i = 0; i < 100000; ++i) {
const folly::IOBuf& obj = *(kObjects[i % kObjects.size()]);
ltHash.addObject({obj.data(), obj.length()});
}
}
BENCHMARK_RELATIVE(calculateChecksumFor100KObjects_B16_N1024) {
LtHash<16, 1024> ltHash;
for (auto i = 0; i < 100000; ++i) {
const folly::IOBuf& obj = *(kObjects[i % kObjects.size()]);
ltHash.addObject({obj.data(), obj.length()});
}
}
BENCHMARK_RELATIVE(calculateChecksumFor100KObjects_B32_N1024) {
LtHash<32, 1024> ltHash;
for (auto i = 0; i < 100000; ++i) {
const folly::IOBuf& obj = *(kObjects[i % kObjects.size()]);
ltHash.addObject({obj.data(), obj.length()});
}
}
BENCHMARK(subtractChecksumFor100KObjects_B20_N1008) {
LtHash<20, 1008> ltHash;
for (auto i = 0; i < 100000; ++i) {
const folly::IOBuf& obj = *(kObjects[i % kObjects.size()]);
ltHash.removeObject({obj.data(), obj.length()});
}
}
BENCHMARK_RELATIVE(subtractChecksumFor100KObjects_B16_N1024) {
LtHash<16, 1024> ltHash;
for (auto i = 0; i < 100000; ++i) {
const folly::IOBuf& obj = *(kObjects[i % kObjects.size()]);
ltHash.removeObject({obj.data(), obj.length()});
}
}
BENCHMARK_RELATIVE(subtractChecksumFor100KObjects_B32_N1024) {
LtHash<32, 1024> ltHash;
for (auto i = 0; i < 100000; ++i) {
const folly::IOBuf& obj = *(kObjects[i % kObjects.size()]);
ltHash.removeObject({obj.data(), obj.length()});
}
}
int main(int argc, char** argv) {
folly::init(&argc, &argv);
if (sodium_init() < 0) {
throw std::runtime_error("Failed to initialize libsodium");
}
// pre-generate objects with random length to hash
for (size_t i = 0; i < kObjectCount; i++) {
kObjects.push_back(makeRandomData(kObjectSize));
}
// Trigger the implementation selection of AUTO math operations before
// starting the benchmark, so log messages don't pollute the output table.
LtHash<20, 1008> ltHash;
ltHash.addObject(folly::range("hello world"));
ltHash.removeObject(folly::range("hello world"));
folly::runBenchmarks();
return 0;
}
/*
* Copyright 2017-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/experimental/crypto/LtHash.h>
#include <folly/Random.h>
#include <folly/String.h>
#include <folly/io/IOBuf.h>
#include <folly/portability/GTest.h>
#include <sodium.h>
#include <algorithm>
#include <memory>
#include <random>
#include <string>
#include <vector>
using namespace ::testing;
using namespace folly::crypto;
namespace {
std::string toHex(const folly::IOBuf& buf) {
return folly::hexlify({buf.data(), buf.length()});
}
std::unique_ptr<folly::IOBuf> makeRandomData(size_t length) {
auto data = folly::IOBuf::create(static_cast<uint64_t>(length));
data->append(length);
randombytes_buf(data->writableData(), data->length());
return data;
}
template <typename T>
struct IsLtHash {
static inline constexpr bool value() {
return false;
}
};
template <std::size_t B, std::size_t N>
struct IsLtHash<LtHash<B, N>> {
static inline constexpr bool value() {
return true;
}
};
} // namespace
// Needed so an EXPECT_EQ() or EXPECT_NE() failure prints the LtHash checksum.
template <std::size_t B, std::size_t N>
static std::ostream& operator<<(std::ostream& os, const LtHash<B, N>& h) {
os << toHex(*h.getChecksum());
return os;
}
// Note: the template parameter H must be an instance of LtHash<B, N> for some
// valid B and N.
template <typename H>
class LtHashTest : public ::testing::Test {
protected:
static_assert(
IsLtHash<H>::value(),
"template parameter H is not an instance of LtHash");
std::array<unsigned char, 1> obj1_{{'a'}};
std::array<unsigned char, 1> obj2_{{'b'}};
static size_t kChecksumLength() {
return H::getElementCount() / H::getElementsPerUint64() * sizeof(uint64_t);
}
static const H& kEmptyHash() {
static const H emptyHash;
return emptyHash;
}
};
// Note: to instantiate template-parameterized test cases, use TYPED_TEST()
// instead of TEST_F().
// Some googletest macro magic to make TYPED_TEST work
using LtHashTestTypes =
::testing::Types<LtHash<16, 1024>, LtHash<20, 1008>, LtHash<32, 1024>>;
TYPED_TEST_CASE(LtHashTest, LtHashTestTypes);
// Note: in all test cases below, `TypeParam` refers to the H template param.
// Static methods must be prefixed with `TestFixture::`, while class variables
// and instance methods must be accessed through `this->`.
//
// See the "Typed Tests" section of the Advanced Guide at
// https://github.com/google/googletest/blob/master/googletest/docs/AdvancedGuide.md
// for details.
TYPED_TEST(LtHashTest, empty) {
TypeParam h;
size_t checksumLength = TestFixture::kChecksumLength();
EXPECT_EQ(checksumLength, h.getChecksumSizeBytes());
auto checksum = h.getChecksum();
EXPECT_EQ(checksumLength, checksum->length());
EXPECT_EQ(std::string(checksumLength * 2, '0'), toHex(*checksum));
}
TYPED_TEST(LtHashTest, reset) {
TypeParam h;
h.addObject(folly::range(this->obj1_));
EXPECT_NE(TestFixture::kEmptyHash(), h);
h.reset();
EXPECT_EQ(TestFixture::kEmptyHash(), h);
}
TYPED_TEST(LtHashTest, copyAssignment) {
TypeParam h1;
h1.addObject(folly::range(this->obj1_));
TypeParam h2{h1};
EXPECT_EQ(h1, h2);
TypeParam h3 = TestFixture::kEmptyHash();
h3 = h1;
EXPECT_EQ(h1, h3);
}
TYPED_TEST(LtHashTest, checksumEquals) {
TypeParam h0;
auto length = h0.getChecksumSizeBytes();
// should throw exception for invalid length checksum
EXPECT_THROW(
h0.checksumEquals(folly::range(std::string(length - 1, '\0'))),
std::runtime_error);
// initial checksum is filled with '0' bytes
EXPECT_TRUE(h0.checksumEquals(folly::range(std::string(length, '\0'))));
auto checksum = h0.getChecksum();
EXPECT_TRUE(h0.checksumEquals({checksum->data(), checksum->length()}));
// add an object and compare checksums
h0.addObject(folly::range(this->obj1_));
checksum = h0.getChecksum();
EXPECT_TRUE(h0.checksumEquals({checksum->data(), checksum->length()}));
// compare against moved-out LtHash, should return false
TypeParam h1 = std::move(h0);
EXPECT_FALSE(h0.checksumEquals({checksum->data(), checksum->length()}));
}
TYPED_TEST(LtHashTest, moveAssignment) {
TypeParam h0;
h0.addObject(folly::range(this->obj1_));
TypeParam h1 = h0;
TypeParam h2{std::move(h1)};
EXPECT_EQ(h0, h2);
TypeParam h3;
h3 = std::move(h2);
EXPECT_EQ(h0, h3);
// Make sure copying to a moved-from object works
h2 = h3;
EXPECT_EQ(h0, h2);
// Move from h3 to make sure calling destructor of a moved-from object
// does not crash.
TypeParam h4 = std::move(h3);
// This line is here to make sure the previous line is not optimized out.
EXPECT_EQ(h0, h4);
}
TYPED_TEST(LtHashTest, addObjectCommutative) {
TypeParam h1;
h1.addObject(folly::range(this->obj1_));
h1.addObject(folly::range(this->obj2_));
TypeParam h2;
h2.addObject(folly::range(this->obj2_));
h2.addObject(folly::range(this->obj1_));
// add('a'); add('b') == add('b'); add('a')
EXPECT_EQ(h1, h2);
}
TYPED_TEST(LtHashTest, addTwoLtHashes) {
TypeParam h1;
h1.addObject(folly::range(this->obj1_));
TypeParam h2;
h2.addObject(folly::range(this->obj2_));
TypeParam h3 = h1 + h2;
TypeParam h4;
h4.addObject(folly::range(this->obj1_));
h4.addObject(folly::range(this->obj2_));
EXPECT_EQ(h4, h3);
// Make sure the move-semantics version works
h3 = std::move(h1) + std::move(h2);
EXPECT_EQ(h4, h3);
}
TYPED_TEST(LtHashTest, subtractTwoLtHashes) {
TypeParam h1;
h1.addObject(folly::range(this->obj1_));
h1.addObject(folly::range(this->obj2_));
TypeParam h2;
h2.addObject(folly::range(this->obj2_));
TypeParam h3 = h1 - h2;
TypeParam h4;
h4.addObject(folly::range(this->obj1_));
EXPECT_EQ(h4, h3);
// Make sure the move-semantics version works
h3 = std::move(h1) - std::move(h2);
EXPECT_EQ(h4, h3);
}
TYPED_TEST(LtHashTest, addObjectChaining) {
TypeParam h1;
h1.addObject(folly::range(this->obj1_));
h1.addObject(folly::range(this->obj2_));
TypeParam h2;
h2.addObject(folly::range(this->obj1_)).addObject(folly::range(this->obj2_));
// add('a'); add('b') == add('a').add('b')
EXPECT_EQ(h1, h2);
}
TYPED_TEST(LtHashTest, addDifferentObjects) {
TypeParam h1;
TypeParam h2;
h1.addObject(folly::range(this->obj1_));
h2.addObject(folly::range(this->obj2_));
// add('a') != add('b')
EXPECT_NE(h1, h2);
}
TYPED_TEST(LtHashTest, addAndremoveObjectSame) {
TypeParam h;
h.addObject(folly::range(this->obj1_));
h.removeObject(folly::range(this->obj1_));
// add('a'); delete('a') == 0
EXPECT_EQ(TestFixture::kEmptyHash(), h);
}
TYPED_TEST(LtHashTest, addObjectWithInitialChecksum) {
TypeParam h1;
h1.addObject(folly::range(this->obj1_));
h1.addObject(folly::range(this->obj2_));
TypeParam h2;
h2.addObject(folly::range(this->obj1_));
TypeParam h3;
h3.setChecksum(*h2.getChecksum());
h3.addObject(folly::range(this->obj2_));
// add('a'); LtHash(checksum) and add('b') == add('a'); add('b');
EXPECT_EQ(h1, h3);
}
TYPED_TEST(LtHashTest, addObjectVariadic) {
std::vector<unsigned char> combinedObj;
for (size_t i = 0; i < this->obj1_.size(); ++i) {
combinedObj.push_back(this->obj1_[i]);
}
for (size_t i = 0; i < this->obj2_.size(); ++i) {
combinedObj.push_back(this->obj2_[i]);
}
TypeParam h1, h2, h3;
h1.addObject(folly::range(combinedObj));
h2.addObject(folly::range(this->obj1_), folly::range(this->obj2_));
EXPECT_EQ(h1, h2);
h3.addObject(folly::range(this->obj2_), folly::range(this->obj1_));
EXPECT_NE(h1, h3);
}
TYPED_TEST(LtHashTest, removeObjectVariadic) {
std::vector<unsigned char> combinedObj;
for (size_t i = 0; i < this->obj1_.size(); ++i) {
combinedObj.push_back(this->obj1_[i]);
}
for (size_t i = 0; i < this->obj2_.size(); ++i) {
combinedObj.push_back(this->obj2_[i]);
}
TypeParam h1;
h1.addObject(folly::range(this->obj1_));
h1.addObject(folly::range(combinedObj));
TypeParam h2 = h1;
TypeParam h3 = h1;
h1.removeObject(folly::range(combinedObj));
h2.removeObject(folly::range(this->obj1_), folly::range(this->obj2_));
EXPECT_EQ(h1, h2);
h3.removeObject(folly::range(this->obj2_), folly::range(this->obj1_));
EXPECT_NE(h1, h3);
}
TYPED_TEST(LtHashTest, getChecksumDeepCopy) {
// verifies that getChecksum() returns a deep copy of the IOBuf
// the returned checksum should not change when a new object is added to
// LtHash later
TypeParam h;
h.addObject(folly::range(this->obj1_));
auto checksum = h.getChecksum();
auto temp = folly::IOBuf::create(checksum->length());
std::memcpy(temp->writableData(), checksum->data(), checksum->length());
EXPECT_EQ(0, std::memcmp(checksum->data(), temp->data(), checksum->length()));
h.addObject(folly::range(this->obj1_));
auto c2 = h.getChecksum();
EXPECT_EQ(0, std::memcmp(checksum->data(), temp->data(), checksum->length()));
EXPECT_NE(0, std::memcmp(checksum->data(), c2->data(), checksum->length()));
}
TYPED_TEST(LtHashTest, addObjectRandomShuffle) {
// 1) generates random objects
// 2) add them to LtHash
// 3) shuffle the object list
// 2) add them to a new LtHash
// 3) then verifies that they reach the same checksum
int objectCount = 1000;
std::vector<std::unique_ptr<folly::IOBuf>> objects;
for (int i = 0; i < objectCount; i++) {
// object size is between 1 byte and 1 KB
size_t objectSize = (folly::Random::rand32() % 1024) + 1;
objects.push_back(makeRandomData(objectSize));
}
TypeParam h1;
for (size_t i = 0; i < objects.size(); i++) {
h1.addObject({objects[i]->data(), objects[i]->length()});
}
auto rng = std::default_random_engine{};
std::shuffle(std::begin(objects), std::end(objects), rng);
TypeParam h2;
for (size_t i = 0; i < objects.size(); i++) {
h2.addObject({objects[i]->data(), objects[i]->length()});
}
// H(o1 + o2 + ...) == H(o_i + o_j + ...)
EXPECT_EQ(h1, h2);
}
TYPED_TEST(LtHashTest, addAndremoveObjectRandom) {
// 1) generate random objects
// 2) adds them to LtHash while storing the checksum after each add
// 3) removes objects in reverse order, verifies that checksum is reversed
TypeParam h;
size_t objectCount = 1000;
std::vector<std::unique_ptr<folly::IOBuf>> objects;
for (size_t i = 0; i < objectCount; i++) {
// object size is between 1 byte and 1 KB
size_t objectSize = (folly::Random::rand32() % 1024) + 1;
objects.push_back(makeRandomData(objectSize));
}
std::vector<std::unique_ptr<folly::IOBuf>> checksums;
checksums.push_back(h.getChecksum());
for (size_t i = 0; i < objects.size(); i++) {
h.addObject({objects[i]->data(), objects[i]->length()});
checksums.push_back(h.getChecksum());
}
EXPECT_TRUE(folly::IOBufEqualTo()(*h.getChecksum(), *checksums.back()));
for (int i = static_cast<int>(objects.size() - 1); i >= 0; i--) {
h.removeObject({objects[i]->data(), objects[i]->length()});
EXPECT_TRUE(folly::IOBufEqualTo()(*h.getChecksum(), *checksums[i]));
}
EXPECT_EQ(TestFixture::kEmptyHash(), h);
}
TYPED_TEST(LtHashTest, setChecksum) {
TypeParam h1;
h1.addObject(folly::range(this->obj1_));
auto checksum = h1.getChecksum();
TypeParam h2(*checksum); // copy version
EXPECT_EQ(h1, h2);
TypeParam h3(h1.getChecksum()); // move version
EXPECT_EQ(h1, h3);
TypeParam h4;
h4.setChecksum(*checksum); // copy version
EXPECT_EQ(h1, h4);
TypeParam h5;
h5.setChecksum(std::move(checksum)); // move version
EXPECT_EQ(h1, h5);
}
TYPED_TEST(LtHashTest, setChecksumWithChainedIOBuf) {
TypeParam h;
h.addObject(folly::range(this->obj1_));
auto c1 = h.getChecksum();
auto c2 = folly::IOBuf::create(c1->length() / 2);
std::memcpy(c2->writableTail(), c1->data(), c1->length() / 2);
c2->append(c1->length() / 2);
auto c3 = folly::IOBuf::create(c1->length() - c2->length());
std::memcpy(
c3->writableTail(),
c1->data() + c2->length(),
c1->length() - c2->length());
c3->append(c1->length() - c2->length());
c2->prependChain(std::move(c3));
TypeParam h2;
h2.setChecksum(*c2);
EXPECT_EQ(h, h2);
}
TYPED_TEST(LtHashTest, setChecksumFailure) {
TypeParam h1;
h1.addObject(folly::range(this->obj1_));
auto checksum = h1.getChecksum();
auto partialChecksum =
folly::IOBuf::copyBuffer(checksum->data(), checksum->length() - 1);
TypeParam h2;
EXPECT_THROW(h2.setChecksum(*partialChecksum), std::runtime_error);
EXPECT_THROW(h2.setChecksum(std::move(partialChecksum)), std::runtime_error);
// Trying to set a null checksum should fail
EXPECT_THROW(
h2.setChecksum(std::unique_ptr<folly::IOBuf>{}), std::runtime_error);
// If padding bits are not properly zeroed out, the checksum is invalid.
if (TypeParam::hasPaddingBits()) {
uint64_t* ptr = reinterpret_cast<uint64_t*>(checksum->writableData());
*ptr = 0xFFFFFFFFFFFFFFFFULL;
EXPECT_THROW(h2.setChecksum(*checksum), std::runtime_error);
EXPECT_THROW(h2.setChecksum(std::move(checksum)), std::runtime_error);
}
}
TYPED_TEST(LtHashTest, addObjectWithKnownValue) {
std::array<unsigned char, 5> obj3{{'h', 'e', 'l', 'l', 'o'}};
TypeParam h;
h.addObject(folly::range(this->obj1_));
h.addObject(folly::range(this->obj2_));
h.addObject(folly::range(obj3));
h.removeObject(folly::range(this->obj2_));
if (h.getElementSizeInBits() == 16 && h.getElementCount() == 1024) {
std::string expectedChecksum =
"353cae6169e519eb9cf80edd2c5b33810276227e77a09030e3ac3b00299c9716c6b592"
"b262b2b05ad82db539f23fc03baa1ffacc9704fe078219307c02c0f501c810895c19a7"
"71934855d091e30db8eba564596f071400fcca93b69115055c55e0b333b5583ec0068a"
"219289b557be5b24cfa679ae8e20b9084c77eadab966e4f94239d5f671371aa17c41f0"
"510aaaeb6e28fed0eb37b57c5ff8f6c64a0395ddb32d2948abee9ae84930ee0d43d015"
"b2f577cadb558eef33e715f349114c1937817ff26b606f1f33a1f3b4a72eaa3b24573a"
"78d06b315857a8295675ec2bfc9897b644f60d401c4315bea8a6ad410f77e3969aaa03"
"2d31526df0c271665647c98f1e4d3946b659e47f45480c3eac9b0e0b742501595b24d5"
"362d3f6f4ba8a4fcda7d87951ade9ec184a45c2fd5bff5282835c29071551e96d940c3"
"ed19bb3124c3b37080dc3c80bc22f61b431195b9489bed3244e0e522bf8f8c752145b0"
"1ee47701085ffa1238f3a1d5e778052b393330fff8b586d9399cced75d4d15697f9015"
"174d3302d97b1cc55ae20cdb573d4061d2940b213a35808122e7d55bd53e2c9ba1779c"
"8a19532ff1e65a440e871f96e086dce6693efba86e033f7e3b04069f9eeccc0f5c5947"
"af0b04f5528be1b57bba0912eeb52fdd11f0cac0e40ae641bbc40207188adbfe13463c"
"880e84016476facae56f7f6de26e7f508a277a409988aabec7f9bb552000e3f7a44f51"
"ec5c7c98979a227403464797a06fae0d7aa951bb429cb9df4ed65a430a98e0c88f7d4e"
"47e1256f17c4b126f05b885154507b3b80a2a1b6e1f43eea48b4b93cab0622bd002a25"
"dd5d1b69fe05c11619837eba6edfac493d663409f5ce82762584205fe49e8f718fbc5a"
"92823cd9a17c1c9a07ce9f2535c918c6ee0f0729b67eb0be8b5e0edc990260679fdf5a"
"9991a6d62ec1d72f5e5a478dbf0e5cbd1703daf5f170411d0d7aca4921cb644ec1d86e"
"02711d09359b0f2b45a5b9fe57e122add8b5ae27aaeb44aa77a9fe187a67ea7447b27d"
"02b4bf41fc5024350dc8838fb8f977535ba4481569a74d90306e0c9979a9d149be9502"
"23d1ca5d425b9ec281ee3884d8e8a1ad0d00504f0f57ff35e0ee33d184f35fd28dfa34"
"8686fb926da95597fb947acc509a5cb7cfe1eeb33dfdf9b4b384346c862cfb198f6948"
"a6f6d53a74848043c8b647076b0a90151bd40c58d32434ebf549aa92f4a5b7581b7ec6"
"821ca3485cf8e2a6ce0f5e204ed5a92c84618c2828e5c6f222ec3c48e37dcada7ce28b"
"ba5c09740170d32aa004b43cde46d45f9912528a3a7a7f30fb6019548dd174b4d7b0ba"
"fb232920b972362db4d863a5e0a9e30a041ecb874a7acbd378ccb11ffbffcd086ad797"
"be5b4de07859d0b1fb3e4835a84ea224940482a3849cf392528dfcf8920d4b4bfc4060"
"6e852d85b7bfd1f2723214969dab6adfb8c26dc5f51b1b043b8a25df1eadd90d1a2324"
"0b735943841ae4e13564ddb6f0f7dcac1db82a34ab9ca042f8c4690727c7a0fac98c10"
"dac065a57dff8010e9d49ba3b801622e8b786bb44079ceecf61ff7cc07be8672c647b5"
"25ffea7c3fab95d40d9d36e220bb3a5292880faf05a8dd94e60a4ff0ccfc124d2dca03"
"a85d0864bfa28cddb7bdcc83ff717239dae979596691b6e3062068e6ea442ebd354bc6"
"53b0e5b750bcbfaec275c77ab82bd3452e4776734df686d6bae946855a4659dd3566f4"
"8d0879a00c06a7ce81c0f234e0203ce68ffc9434f3f10281d76110887a4b460514f761"
"b517f1d151d88724160fbeff7f69a5a23eae2bf48916ad55c084b908d955519a67096b"
"94638fa10d8d153a60d0c44f2d9148ad549fb1e64ac423aac1fdc754bc44a69573578c"
"6b881bca177698e68d6ffdc2d7d89469f2e1039e8e3b955581a56c15519590b65bd9bc"
"c3b3b1a95d1d484c2585ebdfd8a15c737b436456934d9b8439d92d1212bf8799028780"
"d9f35d208c093ba6506aff74979faa10fa807398e8fb769be070318caee6b4f5091d8d"
"9254656d0a1e838ba73ed0f0c8e8d4a0d19f9e91340578baa7ce5aec9f73f8e26db927"
"3c544f11d6b8e5e142f4a8ad70a9e21c3dc2c7b4403073c4722e0af775f98c37ae0645"
"e1829dec574de3108f62965a5354aaa7695c1e4feab1fc8ccf9a5e2a7ed0758e411ea9"
"ea25f4f659a36cc5aa0bed2a9ce4518cd1aa1b4ed94c2d62596059d20cd948a058b78e"
"f9ad3c9e7c7c9fd433c42701aad7aff74fb14ea39812c3e68b6ca8585432ecd53a7dfe"
"ece8e6a73b0ecddabc8c9da37b140adee6308c540bedbcf77d49762e7efaeededf5196"
"6503315fb287b69a08854ec58fd41c2f214c3273cc48bc71718b801c27936c7fd339b7"
"f78c2eda6835e7d532ef6496cbbc7b018cc48ed49e33c16e16d2bdc98f47f376208770"
"b5d5b6e789b30ca55ad4e8cd09b6bd90b66e8d4abfd0fbc3e98fc28f3913e476161d0f"
"0f7477d3ca066adce7567d1af90dc3415970199ada286e22ea90892da107c34d3745c5"
"d5d3f290fbd2d0b64942117955e94b343517d76959f0764216ce27bc33e772fefe4a48"
"20315d67b43302f73e8002cc6144c3f3ad66c307eb2f9e0192a00da9ddf262b600dd0a"
"49721da25ca71997d17c3441cd9588c5469f6927966d06676f89243387f01ebd2094be"
"ac0716a2e486d85524c51ba2898abb8b8df3a169f93fe6333f4f6a868738969905ba55"
"176f1b8055d19749c0c5122ab1eaf34b0eb458fc10a656811fe4a7bb588eac3450b6d6"
"1c7f634588996ef2d903478cde206f58c1a93069c7df80a28394d05e9a8ed99b5312e9"
"cbec0a2dba2e3e3e24e854798e9dc8c09922fadb987e945a765e2f614993f2b5605548"
"1e3702371d4eb86c872ca65269125be61d86";
EXPECT_EQ(expectedChecksum, toHex(*h.getChecksum()));
} else if (h.getElementSizeInBits() == 20 && h.getElementCount() == 1008) {
std::string expectedChecksum =
"aec6e81142ad553e9eae2f5ea2c9d20dec91881229418b20024b05a235b5f50adb98ed"
"276aa9443a7be0047ed20d3f31c8f80ba3d5048719d5174076ac1db9298ec1e62d6b74"
"0e3dd825427e4d1dcf1270eb656e8cddfc16f549cf769e8dc30fec35039986e41e0230"
"a063e4ca145c162766404257f89c2d43f4eb45beddfc036d552dcf94b5fe3780514217"
"52797a3d67d909529399c31ffdb2ce05f725f03fb0892d45ba84602818af60832ea1f1"
"0b7489ee8ce0c8eb01478ca884b4602f197858a0a95d71e83f4796ee61a2a130297691"
"203342e49820f3ab2e78cdedc0190268a81102d08026851eeeeed5600333b6b6cff6df"
"2c7511c1cfc0cb7b85f7283933abffe308d42779698c3f10b8c42058ec4a60ec78270b"
"68104d6dc7fd042124a32bd2c0b4422d3d9ea3c0d878b71ca12d6778a068db04f49aed"
"a11b31160283abef5143e84111c2502012e490f33488ee6098c809aa2870bd63e1dcc9"
"081925fb4622af39ad02322d24de7311fd1dfaa3eb543ec0a5086f6a4f849281322124"
"d7650430409c3cfff6a2d7d2259937ee21eef320657c13cc1fe1689a35a81ee51802a7"
"54ad1b1a8242ed98f18d033550922eb6b191123f803c68a249d9ef1818290085a00870"
"3db27fad9985f42d36469be3edfa450a290194230f2ad9350304ee8cb3e97cb40ca773"
"6e2953c106256f08c4d7972c611cc134049e431c032d91ff0d5cbf549c006f9042cb79"
"3ca7128ca90faf42212f1e1e02877d91f5ec14884440b3df70352ff73ea75c4768541c"
"de0f03f85ef9a824d1a64834ddd9b0303aa543e959813a07950e235a8b1d9e11c7e26e"
"6e42a8c80a89c58f590561be0dd70a6e2f820c9c2c0af3478eecb47513bcef8ff1ccdd"
"9c1830a92b25c518513033856391c009ec37919fa6d5de6ddc32c4cca1a267257b0184"
"17adb3d1e0d9341f6de3143c98d61d625f8aa39fd0723f9773cb84a6c4c2140c0fa0d7"
"ca5d7d15058c0acdd0cd703b19920b2c42c9c30e71392cb6d39c7b1d5794c44af309c6"
"1db784415a80940a129135abd79bac822f067b4631db656d07a53a8f3a212d510f3724"
"c031cab0a51632792ef2477c061fbef3855725a9f90fbe1608498918392ba532c7927a"
"90a3140b0d4309bd31131381270ef120b51720936184f4c4494e05d10082efe281f936"
"10184ef84c198c3ccb77a2e76fd10f31b9afc33a0e51ff2f707842f590719a1368e589"
"69bbc9db0e208a20c957fd663079f2e347184d081abd5382965ca59817d7f40df44e98"
"f42c668169a79fbc0324a36a86e78c2d4507d2672889760577009261e494cc9d3638b9"
"81697bbc71373ed31149804e3497184bcd2e5f2b11310a360b812431792a1a509560e1"
"eb597c3eb6ec09ea32e1823a3400ec0e8af96f38a35c0f623271cb3fee83461da71823"
"288e1a47de8a79f4284ac24daa8bb9493d65d3051198740600774a63e7cbe4b6125aba"
"678c7d94c920e93cab8fc239be312fb2aa214b91fb3dca17a0f4acd1e732838e09c255"
"615c2d9a2d066a705ce334c0cacc3625fcd413b0c5276da3d935393d0a896861a0e415"
"86da248985ed4626fdde66dacdbc97384cb724b11c51d42947dd686376b19004bdc26b"
"b677543d309b4feb60bd88e922fb5227aed9489130d7954b1b65e152337fb545ad83a8"
"6b2ca548e439c268741dbec80181190d232c4fc8c67399e5900b269463069f6c4506f1"
"7e4f507f40ee294e12eb2973b48022d2b7293a6b9501100bf9aa58ce01c82813bfc79e"
"99e80514ce5b6d92f899800edbf34b87f52c121d8de5cc16429072167eee4c7cfcd050"
"3fa42cace2ad2c641f6187a5d42cc8f60cf70d81b2019182274dc64540cf85c62ba229"
"09d82284fe1899fdc9f13761d408c7072563c5400224a3b9ad73127582311691e03ca7"
"9cf71ff31f226fe6cc8a0807494b976854bf0db4dfe02975a0ee2ee66d414aaaada809"
"855fa4bdea98880a7adfa4e81a099a1ad718cee89fa88b0640fbadfbb3e47d00b075a2"
"f5b0e50f34d09c8ef98fa5462559b50f17b9a44e36e9e949c2c4ccac0e2467a7431adc"
"812dc3c2087119f1521bc8080760db2dfa1f4928c97a2b88ce01011921250cf8ca3ba8"
"39461b7868030ec410e2463659ea29ea540bdcde606304504180a147ed052a56150a75"
"2f045320d34ec0fd469c621d29408af4b861e20dda2262b1fdbd4d02739cadd16f64e2"
"339f62c14ef2a5371e1b4de553e5309528e51d262744896132dda9c48983a801021ff3"
"634ec1bd070f5dd5c8ade63de51b348d0c0fb00d003d17720d4673cd5d05e18f69c90f"
"e82c0b1c6661fe60f41a16a285accbf0f47539d8986cf1d78961321a9564ac9b11221e"
"e3be45d9e1dd1a00a35301d968ccf521230ac5aa85a97f35bafea0d27a918d258c1e0d"
"cca7041b2a598a86e291493c140c3e0cbee51c120bdcf1e8ff4ac1ad07f35663f1f1ac"
"be3c8ae58baccb598a127293232631a878351764046d09494914b6e7a7e11b98d427bd"
"d4aa0b0da9f1271ed78d6ea20dbb1a77974b9b16e9b013cc0f85bf6ac5ed23ec048bb5"
"17f4133a98a1a3279375cd0faecc86ba317dfe184177294e430c3d0fb81a8904a3c9a6"
"2d790566069dbcee14b79882268eb88b37becb23791f218b3743b66c281ca0912c380f"
"8d40acedb72885174640d0f824157af0ef2968c93e25679ae4ade784322f31034c5d4f"
"9c2538ea4a800900a55027d2b1239a5bf09209e91a83a402654f06fc0f43800dd46029"
"61a4c58180a0e2252de900de3eb1873ad74707bb962cbf0dd1c7e0672ae9da0a14bbef"
"125408343478dee0549119431b01d9aa8ae850ff133f8a89bde69d8914a316850ee409"
"970a5383a8d491c806307d9082dfa1b9f03f9cbb40889ff509121c9fa1dfda80f52803"
"0de018a1a8c517797b08065d90bd30c8dc67d1dee50d17885a8e0b6ef57a23e290634d"
"1589d0158f9c8a6a7b14471172cbcab20ae5e024918faf959ab8a80c729141f294b8ff"
"07b6af09666aad7c3ebc6ca88e95c1da3057efcd124d7d123bc2d0474d36c4783c1619"
"45181d856007df8dc040d331d2207262a01cd63d7514279def9d4d98243d132e6554c8"
"611b3adfa683d9a3a8a61cca40cb8af3058e3683fc0bdfc1d9bd0f62cd2265db18542b"
"17d9a08671bc6d381d6ee1aa27c5400b4112a0c15d087306581d67006ac1343861ae4d"
"01d938732deae4e9efabf44f1102aa6fbbce2d111f3edb4ac0bc2c0533b186e6632e2c"
"631edde5e4dc9ebce8158f4ee0fa10cd5e046a78c2d03b91bf1a608cad8c248c5e1cb0"
"62847e09850e13fa2187be526d883cdcc168555b1c081ffe8b4d3e1764971bb7b9255c"
"07a4361a857a88d4e059b7300d3e0c769e3ce304ce7048163f7d7b3f16738987faa4ed"
"21d1cb853997341d193947484317584d39e3882691bd29773937184ca49265e93e3fca"
"2cedd84cd0353eac027e456d1c26050661a5444d7b36be4fcd38720c3637e5326e759e"
"9d3838729e0fcc49b4540989ed2e7534a956209b6da9b7f470ff033f23a7f1a6e9101d"
"00d00e9cdcb8482a5b06c18cc6993f29f8d2a7ddcb91e804b82a417c404d761edc6ae6"
"0b81d884268e41c161fcf18f3dec7061e86b046e3831afe93ca869fe06404f4e174d75"
"420686108a011129790b1d4046eae7714e11d980c271eae5f13858d1427caaec9c023e"
"73afdcd76d593922d30a6f04d41f2d8d8a820e0e48171f92ee4ee455700b381fac06e5"
"70c58b0a81bc499a835826117f1146877c90b011ea0ca4db40a17d29";
EXPECT_EQ(expectedChecksum, toHex(*h.getChecksum()));
} else if (h.getElementSizeInBits() == 32 && h.getElementCount() == 1024) {
std::string expectedChecksum =
"68c13c13c663d66f6f834f309cc293115b5c2026dab510ce1c6dae6b13763954ad4771"
"4f93a997a80ca52922770c4e64e3bbf10f67cc87a51d183ec0c66dc9ff751fdc5723cc"
"f8bb6636e0d34e1c7b3a38adcaa2b22e1e9f68bc1524ae89c62a601b1fd1ae544ede9b"
"a826e2153ef1c406d5e6807018e635e677a676031d94a798281d6801792a2aeb22d5ab"
"3082fe6b90b0bb5ffcc71727eb662fb0996e374b4a50074c47f8d68725986e142c98e1"
"38d4c934e17c17ce8ac9b554ed3d0e5016aa8d34976104ccdf63f5309f9a38de29a45f"
"14cd08041f7dd27882891307346495eb98348f06999fae96eba531e7837fcf868ae438"
"2200051f4f5c6ada89584a4a301faa5943ca28c3c0d850fe4754818674fc5a41efdd5b"
"6860c23ad851a5f35ed54dfc70603023b02ae43a8d211dcf7857764559b50aa6cbaa01"
"9fea1485aa0b597efc968e969f1e88add067022a695836fb5da6baacbf4f2278544acd"
"a0b22182788b872a2a75f70fbd2b36ae7dd8c5a291e2e2d2657f583b3852e5183d4bf1"
"5a2b6084cd3f5ef66f2457673aeb13a366ae31ec2cb8c36bf265ffe34af45e8e4e6782"
"0730952b527a955af1fa2dd5a3c4ba3bf94847627e3d049cce0c44c976be8f197f41da"
"a61fa9cf4f95fdc130585d30d897c0364e7a1b162841f7af267bf76888b376dfe7790e"
"14b3d1786e3369c0270bc3d0130c3acfd6a4638b72bd39a9e68e50b16d00a6ba74cf12"
"ccf3ab028e4d728d25c3f99323ffe5c158054b9581cd2a897d24468bb0001337b68b14"
"94fc4ba2f407d2df0d49a593daa31993b4ac16d880efbb94feca0728bd284e0acea435"
"4e386327a086e2e1b991ec31690e0b3eb4a8dffe5c982cf368597980553e2b12e87ccf"
"2e6994c2ea74546a3029a62a9851c5a23b89c2053519ae6304704a19284d8930fa7cca"
"2ab6231e5d620d5743594f11e1248b6b362ba8b0015ca9c7d6bc36d0ae9104221e75c9"
"519e34b0d0888a672cadaca3198d79c214cc275ffee909e600d949e3353cb24e033e19"
"596b4a6d7c299de11337f4d46704f6ad5292fa3eb72535bfd6a27c106a457eca485dd5"
"fdea051666affc73d0e051db5aaa894735c403e2456d0594c42cb0bd1ec325d30ac2dc"
"6aa698a2c1e499a809f9c25f88101d1307b339c7251fc4f455d5d52d1cc08d17ec626a"
"be8ff10ae10b61c0d96b786357dc1385b84f76e1a0fc2fd38ed67f3021ebff3c1655fe"
"20056434a7e68f2569c262f5c1bca2ecc0f9c8160e6b9bfed5fb6d8cf0e74f9df39453"
"9e5a9f04d4a0f2da01023d92ea2e8d2c36ce0b87d07fe6c996bba8795dc81c3c9b7c07"
"26879351817464e4626694aa5ce33ae6ee988f81412c74a8216901fbe46a4adaa75b78"
"39ab01d933bd77b43d8bd4736adbb0507318801f024a044a6a3308cccf4438b6865ecd"
"59521505806a935871c26cfcd4bf3955b2b573eabfbecaf53eb52e92f6fd3e397d4c2a"
"63090ef8b9e2434290e4184aff69bc64dc1246a5e4acf3f02b663c723404911ae4863b"
"fa3222af924647fb47e6ca45dc51bc5e20f771865e73652607006df18ff0d62f688a4d"
"3ed986fa8e79d7f400253d28028f79770d84daa2b14d3aee4a2d4153882f56d693ec7d"
"d9dbca9e1496464e0772abe54c0610dc969952189649a52ceb910cb52a61f989bb993a"
"e948899edd5a08dc4b10eceb47cf68cc3747304dcdde918c4fe60d336af06b0e84afbe"
"e81f39cd3bdfc054f421a12a0d0c8c89b451b3dc385e1ea94aece03cd692ad01237425"
"98fc638ac688c15d1738ef89c07bd19384ea0acbf8e97342c33c71b10993df7c28a36e"
"14278d352a02599478a30a942069dfbb10c7dfc6b921d75eb47f5e6296e17c3a79ef39"
"3d764f167b8c1c6598482497ffe7dd3103ea0c682d15565fe2d75357500482b6dc2848"
"f5a0ef2b7ab697fff5878ec016aa674cf21666cd83b88720ccb6fcfaf9ca9d85f42409"
"471718ec28c85717369f1d9c130b00e18ae7bfab24994bcb582c8e5e9bae67aabf5306"
"8f8d3ae2860c24250f1388b6cda75f2218b674084ee131823b8a1bf2bf70d1028cb99f"
"db3cbb3758e0f03efd6d928900d8ea73f3c9600a593d146ee6d10d437021699ba3fede"
"c2e7942200bfd968a4744b477078b2479f223264e4c845e994558424ebfebfea0cc4af"
"9975b0e59792a28b972d81e2391ad73970842fc7ceab6e217fb40338d107fb5522b0ba"
"c9ae4346600a7444c97f488db697a4a3d63c2b762b1a48eb036a2464bcf5def2c20919"
"bb2bd8ad4f5e00e5a8fc3f77ba8489069b415ed84ff767c6b3e80130688459770bb1bd"
"870d1861f043270ea20a31545a1734134ff406a00d47c65d7e09fb06f714f7a70f20ce"
"cc39bf5e4d4fca8e083fcfc61d8c2212561bc0633dc5c32ff0d922f583ef2b1aa21d83"
"4767708c257d65123d7280f4508bad4c36e162554dfb3a913454b94a4fd78573dc2499"
"f11092ba5b02549f0abf5c325e57d82e749d1e78809cd6484edf8e9b71e1a9b1e58c86"
"b6d82b11ffd1c36a19bb31a6fb38857c4811bd184ae1658b6a3a86175c8d0567ff08cc"
"e74c281b47557a48a1c92a3eee6a903f730ee62e0ef37882dbeb1526a6181f3681ed48"
"529a8af5efdf4c898e84546a18e084223b8077cea0ba9d2fd3d43a2a8560945167e8ef"
"d293a839d086c9b324c3da7a082576fc5e7bbe80b3b2a4057571714eeaf28e5e927fa1"
"f046759dcb5c7bd0b33f1b109ea40b5afaded8c72cfa555f558a2cb581aa4d41216587"
"e9bc03ddfda78471b26aae2b12a265f6f0d1b5a5a61a3e0af9efb96aa606b4d8c6bdaf"
"bab903fb553da5088e7dcebd2666b9c02fc287ec89bfd49a2107f5eac4f60a105c590c"
"a6fba3c9bdce8d385ea8f4b2ee0117f8bf29fe855f27e3ae6f205c43854410a8a5f6b5"
"3c9474d2eeb14bc050df8b8c9b697a510b77f93fe5c41ac0ccf2f13512768e6db2e43f"
"e78e5f8bf61129a91cad0bff6a25d96b568696ea5920f2a4bcc3902df8f929a1d7af3f"
"1e7e37aacf22488dd63a5f33c290d3d57a30a17740210e7f949cc244042fa43f75f25f"
"1da2210cb28e255d57111a647b7b1e2a209545a288fca4f19a1d628c0f5a03e680560f"
"80e7561478cfe222a10844cd5a9257b0ec8bc388d18d6bfbf2cf020448e20c4efaeaea"
"4441c8b82685533ad6c2e657e18d22038fc1fac7e2500b29e86080a54d85661c326646"
"7af9fd33fdc84aa7ce4b5297c9477eec2b45ebaf3c324629f2bfd9fd38eb62c11da2d1"
"abf6291005da15d12910e27cbf02dbe2c02093923668d0dac192c402dd874f7f3692c5"
"fca4b2d9850a6c28dbf0b1d13690cb077174fbaa14c9638d773f5e6ad43a891f0783ac"
"b90da25c894d5b33075ef153a477f76934380dbf2a6b39f3039f43a403387b33137348"
"8cbbcc5534df67c3b1c4323d20d98b4a68e266ce8eff85cc49053449d254e1f8eace18"
"9597731c9b8a5780437c53755706b72a959ce57a2baa10e559e577527c9dacb89fc6bc"
"cbbe250cf6ad229fa68186e7c0687992990d1fac6dfb0af7e6f10f600717d8ece368b9"
"ffb7460f50f8ba1ea58849781ea7f4c369df359c9d08f1e401fe31aafd0ba9a7af0550"
"599c33a1452a7d35dd7ebd241f93d725cb53864c6b0ecae5383d36ed1a1baee0d9a4fb"
"8191091eb68113210a7d2fb7441127987300677eddeb0b3d4104d4e70f40ae0f67468e"
"cf1f7c56e38737ab32dc4e3adbf8bff02cd9c730eab344bb48d4a87625cfbf8b01090a"
"c935e5b63835202945904281659fb1091b3c9ecb48bd2a1192552006f6f6d32d779b86"
"fe3aafb4927972b99be92cc229ef6b0008ac2f8ac782e499442accf4e27eeb19b8e53b"
"46669ee9a53fa78d7e5e03bb40195fec07673f7dcf21c4b71ff1eae78a8728ba7c6658"
"be16cbcfb8bebd400367ffac62b3e5a64a25becea53c62f0dfe3a62273d83dd97bdf82"
"8f4ad6b8c240d4043cf5f8a472e0d5a6caebd3915f68cfe82f30eafd499c6aec35199a"
"bc76fddf895320d5cebd24600532588aa35adc3c117ab71b528b80064b55bf6c7f107a"
"e5465a41a24cc0d59bc3608c101d93c52fd614d37b9a80e912285859d470e4d9815d49"
"9250cf4bd0a85a0d8fd825f37128e62ef37b64a2c0648badd3ccbd448b5ab57e92baee"
"949d2ec36d3c1512316a972f570bb1e44fc8e4d07ab417af6771de2fa66677323b8cbe"
"587f978e5a463f56b0b76e9ce5871d24c80f2a0ef3c57c28338de45871a3632ae1cf7b"
"4437bb1f148ef8bba02911147aa536d140d09e456f41db3151d23c1037a812e4c37a64"
"d95297170729ad8e7b3ed13794e78f16f40c11c50ea3af8723b142fb53e22a9ed9ad47"
"a4a9a919614cf72311f6566cb3b3dfe046530524787e773181e734e86728dbfae34a04"
"5041c83958c509ac68664870a1c7334614506da4692a29e9cf4cb9ac99ac94b3e63dbe"
"f58f80d55863a4a13b3543efff58295f8c690e5fe71534860f67b19c73013c0e6ff52f"
"e0d4c6830023dc56d0473a9797417df439d63d83779097b9f6899a42d6b8a63312aef6"
"f6e75b66efccfcbb63fe4e14002aeb043ec467b68b6bb7fb3ed2e0e850d1c5da0e0015"
"7d8ad7a12c3ec9b6fff04ae03ade0f00ad6d6d76d8bf50f7f9309bcff6184aa3161eee"
"7ffe48b24145389b08a892d2f71efcc9b8be2db9309efd419073519c39993fff210de3"
"ced7eb2e62c6356fc936db012cb2e29b0724e3da7ec9a74042daaf476e150eb0227334"
"0994673cf019525eb3a341e7bb92424ac66c0af6c00a4c94a54bbd65c7de052a6c4d5c"
"cb6e905e609d9c9d93cd28a3993b0df86a7097411e91c46db8471ce2e4645ed7901116"
"fcfd72a117158752b02dfae2e4b2b7be13b8eb01b49066a359fbcb013607d9623fb275"
"711576e948acbacba72fba1c385799b908d14d7ecf57b9d83a59b628c092b686fcf6a9"
"4a5330d95aa8f204a34c4502350564e3657219c914029139fb0fc95a536183e40c1d96"
"d6e1f75073b833c51170cca1a32ca1937fc7e46af88e81346f10aaf88a8e2473444cf6"
"dcef0a178d6f88ec82e8cc79eb6a162d0f177ff4e6ab3b738210f57995a57800dbbe73"
"89349484e6d71813a93c074c55279ad87a64cc0c9e33203a2a275ed59cdc0d8a03aa74"
"6793a93e83868b42fd781d091181455aa28d1d93850766a0b58cdd4fb6d4fd30966da7"
"ab99c42f6c337f8654869803dc372839e3b0e21ec959e59ec07a50e8d11642c4a82b73"
"d07064d265a33f030d397133306d0d69caa3e93238fdc7f78432eea40851a05f683924"
"d7f959c353a66fa11da2c27090505529474ec9f8220794725ab25575e4562f6f94dafc"
"107d5c9fd808471dacf6ec06faa727d7960fb81648551dc248deea3b13bbb941d3d485"
"dec2b60d81c9d8378f25f88dba0d3611137fa0181e8d2b73dddb24aa2f904b97e207cf"
"df4fba7faf36dcb6e7bb857b20751dbaf85608181bcc04d6f23e661ca8595cef86c837"
"75a67825dc7274924e81ef31a01b9a9067ed16cb8c0f2ccd92123b2dc11f847e1467a8"
"9e09259cb63c25b35fb3cf8f20e53667ca44a95f5d7bbe00c50f92cdd970e6c60ae2d6"
"232bc7720c128ef4a4a5e3913be0ae1c5e1c64d10b1b8f24e493c9f543a7cc2ca5eaa9"
"ea208de5ce933340c4f7df1151694c8f24edf4abd8ca09db389b14f4afa2a3be7be8ea"
"0f384384a324c75569278ab5a9a29d48714db5525e63c7bdde225a59d36b9786fc6e09"
"686a79a29884ae47e0cb69a020f9e796cf13fcc53ef08ba265d121a9f7bb35021eb098"
"09";
EXPECT_EQ(expectedChecksum, toHex(*h.getChecksum()));
} else {
FAIL() << "Unexpected element size and/or count: B="
<< h.getElementSizeInBits() << ", N=" << h.getElementCount()
<< ", checksum=" << toHex(*h.getChecksum());
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment