Source: lib/caching/BloomFilter.js

/* eslint-disable no-bitwise,default-case,no-fallthrough */

'use strict';

const atob = require('../util/atob').atob;

/**
 * A Bloom Filter is a client-side kept cache sketch of the server cache
 *
 * @alias caching.BloomFilter
 */
class BloomFilter {
  /**
   * @param {Object} bloomFilter The raw Bloom filter.
   * @param {number} bloomFilter.m The raw Bloom filter bits.
   * @param {number} bloomFilter.h The raw Bloom filter hashes.
   * @param {string} bloomFilter.b The Base64-encoded raw Bloom filter bytes.
   */
  constructor(bloomFilter) {
    /**
     * The raw bytes of this Bloom filter.
     * @type string
     * @readonly
     */
    this.bytes = atob(bloomFilter.b);

    /**
     * The amount of bits.
     * @type number
     * @readonly
     */
    this.bits = bloomFilter.m;

    /**
     * The amount of hashes.
     * @type number
     * @readonly
     */
    this.hashes = bloomFilter.h;

    /**
     * The creation timestamp of this bloom filter.
     * @type number
     * @readonly
     */
    this.creation = Date.now();
  }

  /**
   * Returns whether this Bloom filter contains the given element.
   *
   * @param {string} element The element to check if it is contained.
   * @return {boolean} True, if the element is contained in this Bloom filter.
   */
  contains(element) {
    const hashes = BloomFilter.getHashes(element, this.bits, this.hashes);
    for (let i = 0, len = hashes.length; i < len; i += 1) {
      if (!this.isSet(hashes[i])) {
        return false;
      }
    }
    return true;
  }

  /**
   * Checks whether a bit is set at a given position.
   *
   * @param {number} index The position index to check.
   * @return {boolean} True, if the bit is set at the given position.
   * @private
   */
  isSet(index) {
    const pos = Math.floor(index / 8);
    const bit = 1 << (index % 8);
    // Extract byte as int or NaN if out of range
    const byte = this.bytes.charCodeAt(pos);
    // Bit-wise AND should be non-zero (NaN always yields false)
    return (byte & bit) !== 0;
  }

  /**
   * Returns the hases of a given element in the Bloom filter.
   *
   * @param {string} element The element to check.
   * @param {number} bits The amount of bits.
   * @param {number} hashes The amount of hashes.
   * @return {number[]} The hashes of an element in the Bloom filter.
   * @private
   */
  static getHashes(element, bits, hashes) {
    const hashValues = new Array(this.hashes);
    const hash1 = BloomFilter.murmur3(0, element);
    const hash2 = BloomFilter.murmur3(hash1, element);
    for (let i = 0; i < hashes; i += 1) {
      hashValues[i] = (hash1 + (i * hash2)) % bits;
    }
    return hashValues;
  }

  /**
   * Calculate a Murmur3 hash.
   *
   * @param {number} seed A seed to use for the hashing.
   * @param {string} key A key to check.
   * @return {number} A hashed value of key.
   * @private
   */
  static murmur3(seed, key) {
    const remainder = key.length & 3;
    const bytes = key.length - remainder;
    const c1 = 0xcc9e2d51;
    const c2 = 0x1b873593;
    let h1;
    let h1b;
    let k1;
    let i;
    h1 = seed;
    i = 0;

    while (i < bytes) {
      k1 =
          ((key.charCodeAt(i) & 0xff)) |
          ((key.charCodeAt(i += 1) & 0xff) << 8) |
          ((key.charCodeAt(i += 1) & 0xff) << 16) |
          ((key.charCodeAt(i += 1) & 0xff) << 24);
      i += 1;

      k1 = ((((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16))) & 0xffffffff;
      k1 = (k1 << 15) | (k1 >>> 17);
      k1 = ((((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16))) & 0xffffffff;

      h1 ^= k1;
      h1 = (h1 << 13) | (h1 >>> 19);
      h1b = ((((h1 & 0xffff) * 5) + ((((h1 >>> 16) * 5) & 0xffff) << 16))) & 0xffffffff;
      h1 = (((h1b & 0xffff) + 0x6b64) + ((((h1b >>> 16) + 0xe654) & 0xffff) << 16));
    }

    k1 = 0;

    switch (remainder) {
      case 3:
        k1 ^= (key.charCodeAt(i + 2) & 0xff) << 16;
      case 2:
        k1 ^= (key.charCodeAt(i + 1) & 0xff) << 8;
      case 1:
        k1 ^= (key.charCodeAt(i) & 0xff);

        k1 = (((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff;
        k1 = (k1 << 15) | (k1 >>> 17);
        k1 = (((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff;
        h1 ^= k1;
    }

    h1 ^= key.length;

    h1 ^= h1 >>> 16;
    h1 = (((h1 & 0xffff) * 0x85ebca6b) + ((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) & 0xffffffff;
    h1 ^= h1 >>> 13;
    h1 = ((((h1 & 0xffff) * 0xc2b2ae35) + ((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16))) & 0xffffffff;
    h1 ^= h1 >>> 16;

    return h1 >>> 0;
  }
}

module.exports = BloomFilter;