From 65ab296d2e955276f0b5fc9c661e0496c3600cb3 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 11 Jan 2017 12:10:04 -0800 Subject: [PATCH] bloom: cleanup instantiation. --- lib/net/packets.js | 2 +- lib/utils/bloom.js | 178 ++++++++++++++++++++++++++++++++++----------- 2 files changed, 138 insertions(+), 42 deletions(-) diff --git a/lib/net/packets.js b/lib/net/packets.js index b71b2c12..061fc793 100644 --- a/lib/net/packets.js +++ b/lib/net/packets.js @@ -2199,7 +2199,7 @@ function FilterLoadPacket(filter) { Packet.call(this); - this.filter = filter || new Bloom(0, 0, 0, -1); + this.filter = filter || new Bloom(); } util.inherits(FilterLoadPacket, Packet); diff --git a/lib/utils/bloom.js b/lib/utils/bloom.js index 258f5739..da515725 100644 --- a/lib/utils/bloom.js +++ b/lib/utils/bloom.js @@ -14,6 +14,7 @@ var StaticWriter = require('./staticwriter'); var encoding = require('./encoding'); var sum32 = murmur3.sum32; var mul32 = murmur3.mul32; +var DUMMY = new Buffer(0); /* * Constants @@ -29,6 +30,7 @@ var LN2 = 0.6931471805599453094172321214581765680755001343602552; * @param {Number|Bufer} size - Filter size in bits, or filter itself. * @param {Number} n - Number of hash functions. * @param {Number} tweak - Seed value. + * @param {Number|String} - Update type. * @property {Buffer} filter * @property {Number} size * @property {Number} n @@ -40,27 +42,14 @@ 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.size = size - (size % 8); - this.filter = new Buffer(this.size / 8); - this.reset(); - } + this.filter = DUMMY; + this.size = 0; + this.n = 0; + this.tweak = 0; + this.update = Bloom.flags.NONE; - if (tweak == null || tweak === -1) - tweak = (Math.random() * 0x100000000) >>> 0; - - if (update == null || update === -1) - update = Bloom.flags.NONE; - - if (typeof update === 'string') - update = Bloom.flags[update.toUpperCase()]; - - this.n = n; - this.tweak = tweak; - this.update = update; + if (size != null) + this.fromOptions(size, n, tweak, update); } /** @@ -117,6 +106,68 @@ Bloom.MAX_BLOOM_FILTER_SIZE = 36000; Bloom.MAX_HASH_FUNCS = 50; +/** + * Inject properties from options. + * @private + * @param {Number|Bufer} size - Filter size in bits, or filter itself. + * @param {Number} n - Number of hash functions. + * @param {Number} tweak - Seed value. + * @param {Number|String} - Update type. + * @returns {Bloom} + */ + +Bloom.prototype.fromOptions = function fromOptions(size, n, tweak, update) { + var filter; + + if (Buffer.isBuffer(size)) { + filter = size; + size = filter.length * 8; + } else { + assert(typeof size === 'number', '`size` must be a number.'); + assert(size > 0, '`size` must be greater than zero.'); + size -= size % 8; + filter = new Buffer(size / 8); + filter.fill(0); + } + + if (tweak == null || tweak === -1) + tweak = (Math.random() * 0x100000000) >>> 0; + + if (update == null || update === -1) + update = Bloom.flags.NONE; + + if (typeof update === 'string') { + update = Bloom.flags[update.toUpperCase()]; + assert(update != null, 'Unknown update flag.'); + } + + assert(size > 0, '`size` must be greater than zero.'); + assert(n > 0, '`n` must be greater than zero.'); + assert(typeof tweak === 'number', '`tweak` must be a number.'); + assert(Bloom.flagsByVal[update], 'Unknown update flag.'); + + this.filter = filter; + this.size = size; + this.n = n; + this.tweak = tweak; + this.update = update; + + return this; +}; + +/** + * Instantiate bloom filter from options. + * @param {Number|Bufer} size - Filter size in bits, or filter itself. + * @param {Number} n - Number of hash functions. + * @param {Number} tweak - Seed value. + * @param {Number|String} - Update type. + * @returns {Bloom} + */ + +Bloom.fromOptions = function fromOptions(size, n, tweak, update) { + return new Bloom().fromOptions(size, n, tweak, update); +}; + /** * Perform the mumur3 hash on data. * @param {Buffer} val @@ -203,11 +254,11 @@ Bloom.prototype.added = function added(val, enc) { /** * Create a filter from a false positive rate. - * @param {Number} items - Expeected number of items. + * @param {Number} items - Expected number of items. * @param {Number} rate - False positive rate (0.0-1.0). * @param {Number|String} update * @example - * bcoin.bloom.fromRate(800000, 0.01, 'none'); + * Bloom.fromRate(800000, 0.0001, 'none'); * @returns {Boolean} */ @@ -219,7 +270,7 @@ Bloom.fromRate = function fromRate(items, rate, update) { if (update !== -1) size = Math.min(size, Bloom.MAX_BLOOM_FILTER_SIZE * 8); - n = ((size / items * LN2) | 0) || 1; + n = Math.max(1, size / items * LN2); if (update !== -1) n = Math.min(n, Bloom.MAX_HASH_FUNCS); @@ -285,7 +336,7 @@ Bloom.prototype.fromReader = function fromReader(br) { this.n = br.readU32(); this.tweak = br.readU32(); this.update = br.readU8(); - assert(Bloom.flagsByVal[this.update] != null, 'Bad filter flag.'); + assert(Bloom.flagsByVal[this.update] != null, 'Unknown update flag.'); return this; }; @@ -332,31 +383,76 @@ Bloom.fromRaw = function fromRaw(data, enc) { */ function RollingFilter(items, rate) { - var logRate, max; - if (!(this instanceof RollingFilter)) return new RollingFilter(items, rate); - logRate = Math.log(rate); - this.entries = 0; this.generation = 1; + this.n = 0; + this.limit = 0; + this.size = 0; + this.items = 0; + this.tweak = 0; + this.filter = DUMMY; - this.n = Math.max(1, Math.min(Math.round(logRate / Math.log(0.5)), 50)); - this.limit = (items + 1) / 2 | 0; - - max = this.limit * 3; - this.size = -1 * this.n * max / Math.log(1.0 - Math.exp(logRate / this.n)); - this.size = Math.ceil(this.size); - - this.items = ((this.size + 63) / 64 | 0) << 1; - - this.tweak = (Math.random() * 0x100000000) >>> 0; - - this.filter = new Buffer(this.items * 8); - this.filter.fill(0); + if (items != null) + this.fromRate(items, rate); } +/** + * Inject properties from items and FPR. + * @private + * @param {Number} items - Expected number of items. + * @param {Number} rate - False positive rate (0.0-1.0). + * @returns {RollingFilter} + */ + +RollingFilter.prototype.fromRate = function fromRate(items, rate) { + var logRate, max, n, limit, size, items, tweak, filter; + + assert(typeof items === 'number', '`items` must be a number.'); + assert(items > 0, '`items` must be greater than zero.'); + assert(typeof rate === 'number', '`rate` must be a number.'); + assert(rate >= 0, '`rate` must be positive.'); + + logRate = Math.log(rate); + + n = Math.max(1, Math.min(Math.round(logRate / Math.log(0.5)), 50)); + limit = (items + 1) / 2 | 0; + + max = limit * 3; + size = -1 * n * max / Math.log(1.0 - Math.exp(logRate / n)); + size = Math.ceil(size); + + items = ((size + 63) / 64 | 0) << 1; + items >>>= 0; + + tweak = (Math.random() * 0x100000000) >>> 0; + + filter = new Buffer(items * 8); + filter.fill(0); + + this.n = n; + this.limit = limit; + this.size = size; + this.items = items; + this.tweak = tweak; + this.filter = filter; + + return this; +}; + +/** + * Instantiate rolling filter from items and FPR. + * @param {Number} items - Expected number of items. + * @param {Number} rate - False positive rate (0.0-1.0). + * @returns {RollingFilter} + */ + +RollingFilter.fromRate = function fromRate(items, rate) { + return new RollingFilter().fromRate(items, rate); +}; + /** * Perform the mumur3 hash on data. * @param {Buffer} val