Skip to content

Commit 4f545b1

Browse files
committed
feat(ObjectID): use FNV-1a hash for objectId
Uses a hash of the hostname in the object id. Hashing algorithm is FNV-1a, 24-bit, using xor-folding. Fixes NODE-1484
1 parent 8cb0cf7 commit 4f545b1

File tree

5 files changed

+1697
-1
lines changed

5 files changed

+1697
-1
lines changed

lib/bson/fnv1a.js

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
'use strict';
2+
3+
const Long = require('./long');
4+
5+
const MASK_8 = 0xff;
6+
const MASK_24 = 0xffffff;
7+
const MASK_32 = 0xffffffff;
8+
9+
// See http://www.isthe.com/chongo/tech/comp/fnv/#FNV-param for the definition of these parameters;
10+
const FNV_PRIME = new Long(16777619, 0);
11+
const OFFSET_BASIS = new Long(2166136261, 0);
12+
const FNV_MASK = new Long(MASK_32, 0);
13+
14+
function fnv1a32(input, encoding) {
15+
encoding = encoding || 'utf8';
16+
const octets = Buffer.from(input, encoding);
17+
18+
let hash = OFFSET_BASIS;
19+
for (let i = 0; i < octets.length; i += 1) {
20+
hash = hash.xor(new Long(octets[i], 0));
21+
hash = hash.multiply(FNV_PRIME);
22+
hash = hash.and(FNV_MASK);
23+
}
24+
return hash.getLowBitsUnsigned();
25+
}
26+
27+
function fnv1a24(input, encoding) {
28+
const _32bit = fnv1a32(input, encoding);
29+
const base = _32bit & MASK_24;
30+
const top = (_32bit >>> 24) & MASK_8;
31+
const final = (base ^ top) & MASK_24;
32+
33+
return final;
34+
}
35+
36+
module.exports = { fnv1a24, fnv1a32 };

lib/bson/objectid.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
'use strict';
2+
3+
const hostname = require('os').hostname;
4+
const fnv1a24 = require('./fnv1a').fnv1a24;
5+
26
/**
37
* Machine id.
48
*
@@ -7,7 +11,7 @@
711
* that would mean an asyc call to gethostname, so we don't bother.
812
* @ignore
913
*/
10-
var MACHINE_ID = parseInt(Math.random() * 0xffffff, 10);
14+
const MACHINE_ID = fnv1a24(hostname);
1115

1216
// Regular expression that checks for hex value
1317
var checkForHexRegExp = new RegExp('^[0-9a-fA-F]{24}$');

test/node/fnv1a.js

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
'use strict';
2+
3+
const fnv1a = require('../../lib/bson/fnv1a');
4+
const fnv1a24 = fnv1a.fnv1a24;
5+
const expect = require('chai').expect;
6+
describe('fnv1a', function() {
7+
require('./specs/object-id/vectors.json').vectors.forEach(testCase => {
8+
const hash = testCase.hash;
9+
10+
let vector;
11+
let encoding;
12+
if (typeof testCase.vector === 'string') {
13+
vector = testCase.vector;
14+
encoding = 'utf8';
15+
} else if (typeof testCase.vectorHex === 'string') {
16+
vector = testCase.vectorHex;
17+
encoding = 'hex';
18+
}
19+
20+
it(`should properly hash the string "${vector}" with a 24 bit FNV-1a`, function() {
21+
const hashed = fnv1a24(vector, encoding);
22+
const buff = Buffer.from([(hashed >>> 16) & 0xff, (hashed >>> 8) & 0xff, hashed & 0xff]);
23+
expect(buff.toString('hex')).to.equal(hash);
24+
});
25+
});
26+
});

0 commit comments

Comments
 (0)