210 lines
3.7 KiB
JavaScript
210 lines
3.7 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('bsert');
|
|
const Path = require('path');
|
|
const fs = require('bfile');
|
|
const sha3 = require('bcrypto/lib/sha3');
|
|
|
|
/*
|
|
* Constants
|
|
*/
|
|
|
|
const FILE = Path.resolve(__dirname, 'names.db');
|
|
const DATA = fs.readFileSync(FILE);
|
|
|
|
/**
|
|
* Reserved
|
|
*/
|
|
|
|
class Reserved {
|
|
constructor(data) {
|
|
this.data = data;
|
|
this.size = readU32(data, 0);
|
|
this.nameValue = readU64(data, 4);
|
|
this.rootValue = readU64(data, 12);
|
|
this.topValue = readU64(data, 20);
|
|
}
|
|
|
|
_compare(b, off) {
|
|
const a = this.data;
|
|
|
|
for (let i = 0; i < 32; i++) {
|
|
const x = a[off + i];
|
|
const y = b[i];
|
|
|
|
if (x < y)
|
|
return -1;
|
|
|
|
if (x > y)
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
_find(key) {
|
|
let start = 0;
|
|
let end = this.size - 1;
|
|
|
|
while (start <= end) {
|
|
const index = (start + end) >>> 1;
|
|
const pos = 28 + index * 36;
|
|
const cmp = this._compare(key, pos);
|
|
|
|
if (cmp === 0)
|
|
return readU32(this.data, pos + 32);
|
|
|
|
if (cmp < 0)
|
|
start = index + 1;
|
|
else
|
|
end = index - 1;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
_target(pos) {
|
|
const len = this.data[pos];
|
|
return this.data.toString('ascii', pos + 1, pos + 1 + len);
|
|
}
|
|
|
|
_flags(pos) {
|
|
const len = this.data[pos];
|
|
return this.data[pos + 1 + len];
|
|
}
|
|
|
|
_index(pos) {
|
|
const len = this.data[pos];
|
|
return this.data[pos + 1 + len + 1];
|
|
}
|
|
|
|
_value(pos) {
|
|
const len = this.data[pos];
|
|
const off = pos + 1 + len + 1 + 1;
|
|
return readU64(this.data, off);
|
|
}
|
|
|
|
_get(hash, pos) {
|
|
const target = this._target(pos);
|
|
const flags = this._flags(pos);
|
|
const index = this._index(pos);
|
|
const root = (flags & 1) !== 0;
|
|
const top100 = (flags & 2) !== 0;
|
|
const custom = (flags & 4) !== 0;
|
|
const zero = (flags & 8) !== 0;
|
|
const name = target.substring(0, index);
|
|
|
|
let value = this.nameValue;
|
|
|
|
if (root)
|
|
value += this.rootValue;
|
|
|
|
if (top100)
|
|
value += this.topValue;
|
|
|
|
if (custom)
|
|
value += this._value(pos);
|
|
|
|
if (zero)
|
|
value = 0;
|
|
|
|
return {
|
|
name,
|
|
hash,
|
|
target,
|
|
value,
|
|
root,
|
|
top100,
|
|
custom,
|
|
zero
|
|
};
|
|
}
|
|
|
|
has(hash) {
|
|
assert(Buffer.isBuffer(hash) && hash.length === 32);
|
|
|
|
return this._find(hash) !== -1;
|
|
}
|
|
|
|
get(hash) {
|
|
assert(Buffer.isBuffer(hash) && hash.length === 32);
|
|
|
|
const pos = this._find(hash);
|
|
|
|
if (pos === -1)
|
|
return null;
|
|
|
|
return this._get(hash, pos);
|
|
}
|
|
|
|
hasByName(name) {
|
|
assert(typeof name === 'string');
|
|
|
|
if (name.length === 0 || name.length > 63)
|
|
return false;
|
|
|
|
return this.has(hashName(name));
|
|
}
|
|
|
|
getByName(name) {
|
|
assert(typeof name === 'string');
|
|
|
|
if (name.length === 0 || name.length > 63)
|
|
return null;
|
|
|
|
return this.get(hashName(name));
|
|
}
|
|
|
|
*entries() {
|
|
for (let i = 0; i < this.size; i++) {
|
|
const pos = 28 + i * 36;
|
|
const hash = this.data.slice(pos, pos + 32);
|
|
const ptr = readU32(this.data, pos + 32);
|
|
const item = this._get(hash, ptr);
|
|
|
|
yield [hash, item];
|
|
}
|
|
}
|
|
|
|
*keys() {
|
|
for (let i = 0; i < this.size; i++) {
|
|
const pos = 28 + i * 36;
|
|
|
|
yield this.data.slice(pos, pos + 32);
|
|
}
|
|
}
|
|
|
|
*values() {
|
|
for (const [, item] of this.entries())
|
|
yield item;
|
|
}
|
|
|
|
[Symbol.iterator]() {
|
|
return this.entries();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Helpers
|
|
*/
|
|
|
|
function readU32(data, off) {
|
|
return data.readUInt32LE(off);
|
|
}
|
|
|
|
function readU64(data, off) {
|
|
const lo = data.readUInt32LE(off);
|
|
const hi = data.readUInt32LE(off + 4);
|
|
return hi * 0x100000000 + lo;
|
|
}
|
|
|
|
function hashName(name) {
|
|
const raw = Buffer.from(name.toLowerCase(), 'ascii');
|
|
return sha3.digest(raw);
|
|
}
|
|
|
|
/*
|
|
* Expose
|
|
*/
|
|
|
|
module.exports = new Reserved(DATA);
|