fcoin/lib/bcoin/bloom.js
Christopher Jeffrey f8d49f0653
more utils cleanup.
2016-04-18 01:11:02 -07:00

239 lines
4.5 KiB
JavaScript

/*!
* bloom.js - bloom filter for bcoin
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
* Copyright (c) 2014-2016, Christopher Jeffrey (MIT License).
* https://github.com/indutny/bcoin
*/
var utils = require('./utils');
var assert = utils.assert;
/**
* Bloom Filter
* @exports Bloom
* @constructor
* @param {Number|Bufer} size - Filter size in bytes, or filter itself.
* @param {Number} n - Number of hash functions.
* @param {Number} tweak - Seed value.
* @property {Buffer} filter
* @property {Number} size
* @property {Number} n
* @property {Number} tweak
* @property {Number} update - Update flag (see {@link constants.filterFlags}).
*/
function Bloom(size, n, tweak, update) {
if (!(this instanceof Bloom))
return new Bloom(size, n, tweak, update);
if (Buffer.isBuffer(size)) {
this.filter = size;
this.size = this.filter.length * 8;
} else {
this.filter = new Buffer(Math.ceil(size / 8));
this.size = size;
this.reset();
}
this.n = n;
this.tweak = tweak;
this.update = update;
}
/**
* Perform the mumur3 hash on data.
* @param {Buffer} val
* @param {Number} seed
* @returns {Number}
*/
Bloom.prototype.hash = function hash(val, n) {
return murmur(val, sum32(mul32(n, 0xfba4c795), this.tweak)) % this.size;
};
/**
* Reset the filter.
*/
Bloom.prototype.reset = function reset() {
this.filter.fill(0);
};
/**
* Add data to the filter.
* @param {Buffer|String}
* @param {String?} enc - Can be any of the Buffer object's encodings.
*/
Bloom.prototype.add = function add(val, enc) {
var i, bit, pos, shift;
if (typeof val === 'string')
val = new Buffer(val, enc);
for (i = 0; i < this.n; i++) {
bit = this.hash(val, i);
pos = 1 << (bit & 0x1f);
shift = bit >> 5;
shift *= 4;
utils.writeU32(this.filter, utils.readU32(this.filter, shift) | pos, shift);
}
};
/**
* Test whether data is present in the filter.
* @param {Buffer|String} val
* @param {String?} enc - Can be any of the Buffer object's encodings.
* @returns {Boolean}
*/
Bloom.prototype.test = function test(val, enc) {
var i, bit, pos, shift;
if (typeof val === 'string')
val = new Buffer(val, enc);
for (i = 0; i < this.n; i++) {
bit = this.hash(val, i);
pos = 1 << (bit & 0x1f);
shift = bit >> 5;
shift *= 4;
if ((utils.readU32(this.filter, shift) & pos) === 0)
return false;
}
return true;
};
/**
* Return a Buffer representing the filter,
* suitable for transmission on the network.
* @returns {Buffer}
*/
Bloom.prototype.toBuffer = function toBuffer() {
return this.filter;
};
/**
* Ensure the filter is within the size limits.
* @returns {Boolean}
*/
Bloom.prototype.isWithinConstraints = function isWithinConstraints() {
if (this.filter.length > constants.bloom.MAX_BLOOM_FILTER_SIZE)
return false;
if (this.n > constants.bloom.MAX_HASH_FUNCS)
return false;
return true;
};
/*
* Murmur
*/
function mul32(a, b) {
var alo = a & 0xffff;
var blo = b & 0xffff;
var ahi = a >>> 16;
var bhi = b >>> 16;
var r, lo, hi;
lo = alo * blo;
hi = (ahi * blo + bhi * alo) & 0xffff;
hi += lo >>> 16;
lo &= 0xffff;
r = (hi << 16) | lo;
if (r < 0)
r += 0x100000000;
return r;
}
function sum32(a, b) {
var r = (a + b) & 0xffffffff;
if (r < 0)
r += 0x100000000;
return r;
}
function rotl32(w, b) {
return (w << b) | (w >>> (32 - b));
}
function murmur(data, seed) {
var c1 = 0xcc9e2d51;
var c2 = 0x1b873593;
var r1 = 15;
var r2 = 13;
var m = 5;
var n = 0xe6546b64;
var hash = seed;
var i, w, r, j;
assert(Buffer.isBuffer(data));
for (i = 0; i + 4 <= data.length; i += 4) {
w = data[i]
| (data[i + 1] << 8)
| (data[i + 2] << 16)
| (data[i + 3] << 24);
w = mul32(w, c1);
w = rotl32(w, r1);
w = mul32(w, c2);
hash ^= w;
hash = rotl32(hash, r2);
hash = mul32(hash, m);
hash = sum32(hash, n);
}
if (i !== data.length) {
r = 0;
for (j = data.length - 1; j >= i; j--)
r = (r << 8) | data[j];
r = mul32(r, c1);
r = rotl32(r, r1);
if (r < 0)
r += 0x100000000;
r = mul32(r, c2);
hash ^= r;
}
hash ^= data.length;
hash ^= hash >>> 16;
hash = mul32(hash, 0x85ebca6b);
hash ^= hash >>> 13;
hash = mul32(hash, 0xc2b2ae35);
hash ^= hash >>> 16;
if (hash < 0)
hash += 0x100000000;
return hash;
}
/**
* Murmur3 hash.
* @static
* @function
* @param {Buffer} data
* @param {Number} seed
* @returns {Number}
*/
Bloom.hash = murmur;
module.exports = Bloom;