From b81643473e8b6de0f5e4a996a7b861c49394a12d Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 5 Sep 2017 21:17:58 -0700 Subject: [PATCH] encoding: refactor int64 handling. --- lib/blockchain/chaindb.js | 6 +- lib/net/packets.js | 4 +- lib/utils/encoding.js | 296 +++++++++----------------------------- lib/utils/reader.js | 79 +--------- lib/wallet/masterkey.js | 2 +- lib/wallet/txdb.js | 8 +- migrate/coins-old.js | 2 +- migrate/coins/compress.js | 2 +- 8 files changed, 80 insertions(+), 319 deletions(-) diff --git a/lib/blockchain/chaindb.js b/lib/blockchain/chaindb.js index cf742bce0..c1c184df8 100644 --- a/lib/blockchain/chaindb.js +++ b/lib/blockchain/chaindb.js @@ -2105,9 +2105,9 @@ ChainState.fromRaw = function fromRaw(data) { const state = new ChainState(); const br = new BufferReader(data); state.tip = br.readHash(); - state.tx = br.readU53(); - state.coin = br.readU53(); - state.value = br.readU53(); + state.tx = br.readU64(); + state.coin = br.readU64(); + state.value = br.readU64(); return state; }; diff --git a/lib/net/packets.js b/lib/net/packets.js index c1702512f..efb7ffbdd 100644 --- a/lib/net/packets.js +++ b/lib/net/packets.js @@ -291,7 +291,7 @@ VersionPacket.prototype.fromReader = function fromReader(br) { // are currently unused. br.readU32(); - this.time = br.readI53(); + this.time = br.readI64(); this.remote.fromReader(br, false); if (br.left() > 0) { @@ -2209,7 +2209,7 @@ SendCmpctPacket.prototype.toRaw = function toRaw() { SendCmpctPacket.prototype.fromReader = function fromReader(br) { this.mode = br.readU8(); - this.version = br.readU53(); + this.version = br.readU64(); return this; }; diff --git a/lib/utils/encoding.js b/lib/utils/encoding.js index a98fbe2c0..81c7db34d 100644 --- a/lib/utils/encoding.js +++ b/lib/utils/encoding.js @@ -166,36 +166,6 @@ encoding.ZERO_U32 = Buffer.from('00000000', 'hex'); encoding.ZERO_U64 = Buffer.from('0000000000000000', 'hex'); -/** - * Read uint64 as a js number. - * @private - * @param {Buffer} data - * @param {Number} off - * @param {Boolean} force53 - Read only 53 bits, but maintain the sign. - * @param {Boolean} be - * @returns {Number} - * @throws on num > MAX_SAFE_INTEGER - */ - -encoding._readU64 = function _readU64(data, off, force53, be) { - let hi, lo; - - if (be) { - hi = data.readUInt32BE(off, true); - lo = data.readUInt32BE(off + 4, true); - } else { - hi = data.readUInt32LE(off + 4, true); - lo = data.readUInt32LE(off, true); - } - - if (force53) - hi &= 0x1fffff; - - enforce((hi & 0xffe00000) === 0, off, 'Number exceeds 2^53-1'); - - return (hi * 0x100000000) + lo; -}; - /** * Read uint64le as a js number. * @param {Buffer} data @@ -205,7 +175,10 @@ encoding._readU64 = function _readU64(data, off, force53, be) { */ encoding.readU64 = function readU64(data, off) { - return encoding._readU64(data, off, false, false); + const hi = data.readUInt32LE(off + 4, true); + const lo = data.readUInt32LE(off, true); + enforce((hi & 0xffe00000) === 0, off, 'Number exceeds 2^53-1'); + return hi * 0x100000000 + lo; }; /** @@ -217,72 +190,9 @@ encoding.readU64 = function readU64(data, off) { */ encoding.readU64BE = function readU64BE(data, off) { - return encoding._readU64(data, off, false, true); -}; - -/** - * Read uint64le as a js number (limit at 53 bits). - * @param {Buffer} data - * @param {Number} off - * @returns {Number} - * @throws on num > MAX_SAFE_INTEGER - */ - -encoding.readU53 = function readU53(data, off) { - return encoding._readU64(data, off, true, false); -}; - -/** - * Read uint64be as a js number (limit at 53 bits). - * @param {Buffer} data - * @param {Number} off - * @returns {Number} - * @throws on num > MAX_SAFE_INTEGER - */ - -encoding.readU53BE = function readU53BE(data, off) { - return encoding._readU64(data, off, true, true); -}; - -/** - * Read int64 as a js number. - * @private - * @param {Buffer} data - * @param {Number} off - * @param {Boolean} force53 - Read only 53 bits, but maintain the sign. - * @param {Boolean} be - * @returns {Number} - * @throws on num > MAX_SAFE_INTEGER - */ - -encoding._readI64 = function _readI64(data, off, force53, be) { - let hi, lo; - - if (be) { - hi = data.readUInt32BE(off, true); - lo = data.readUInt32BE(off + 4, true); - } else { - hi = data.readUInt32LE(off + 4, true); - lo = data.readUInt32LE(off, true); - } - - if (hi & 0x80000000) { - hi = ~hi >>> 0; - lo = ~lo >>> 0; - - if (force53) - hi &= 0x1fffff; - - enforce((hi & 0xffe00000) === 0, off, 'Number exceeds 2^53-1'); - - return -(hi * 0x100000000 + lo + 1); - } - - if (force53) - hi &= 0x1fffff; - + const hi = data.readUInt32BE(off, true); + const lo = data.readUInt32BE(off + 4, true); enforce((hi & 0xffe00000) === 0, off, 'Number exceeds 2^53-1'); - return hi * 0x100000000 + lo; }; @@ -295,7 +205,10 @@ encoding._readI64 = function _readI64(data, off, force53, be) { */ encoding.readI64 = function readI64(data, off) { - return encoding._readI64(data, off, false, false); + const hi = data.readInt32LE(off + 4, true); + const lo = data.readUInt32LE(off, true); + enforce(isSafe(hi, lo), 'Number exceeds 2^53-1'); + return hi * 0x100000000 + lo; }; /** @@ -307,85 +220,10 @@ encoding.readI64 = function readI64(data, off) { */ encoding.readI64BE = function readI64BE(data, off) { - return encoding._readI64(data, off, false, true); -}; - -/** - * Read int64be as a js number (limit at 53 bits). - * @param {Buffer} data - * @param {Number} off - * @returns {Number} - * @throws on num > MAX_SAFE_INTEGER - */ - -encoding.readI53 = function readI53(data, off) { - return encoding._readI64(data, off, true, false); -}; - -/** - * Read int64be as a js number (limit at 53 bits). - * @param {Buffer} data - * @param {Number} off - * @returns {Number} - * @throws on num > MAX_SAFE_INTEGER - */ - -encoding.readI53BE = function readI53BE(data, off) { - return encoding._readI64(data, off, true, true); -}; - -/** - * Write a javascript number as an int64. - * @private - * @param {Buffer} dst - * @param {Number} num - * @param {Number} off - * @param {Boolean} be - * @returns {Number} Buffer offset. - * @throws on num > MAX_SAFE_INTEGER - */ - -encoding._writeI64 = function _writeI64(dst, num, off, be) { - const neg = num < 0; - - if (neg) { - num = -num; - num -= 1; - } - - enforce(num <= MAX_SAFE_INTEGER, off, 'Number exceeds 2^53-1'); - - let hi = (num * (1 / 0x100000000)) | 0; - let lo = num | 0; - - if (neg) { - hi = ~hi; - lo = ~lo; - } - - if (be) { - dst[off++] = hi >>> 24; - dst[off++] = (hi >> 16) & 0xff; - dst[off++] = (hi >> 8) & 0xff; - dst[off++] = hi & 0xff; - - dst[off++] = lo >>> 24; - dst[off++] = (lo >> 16) & 0xff; - dst[off++] = (lo >> 8) & 0xff; - dst[off++] = lo & 0xff; - } else { - dst[off++] = lo & 0xff; - dst[off++] = (lo >> 8) & 0xff; - dst[off++] = (lo >> 16) & 0xff; - dst[off++] = lo >>> 24; - - dst[off++] = hi & 0xff; - dst[off++] = (hi >> 8) & 0xff; - dst[off++] = (hi >> 16) & 0xff; - dst[off++] = hi >>> 24; - } - - return off; + const hi = data.readInt32BE(off, true); + const lo = data.readUInt32BE(off + 4, true); + enforce(isSafe(hi, lo), 'Number exceeds 2^53-1'); + return hi * 0x100000000 + lo; }; /** @@ -398,7 +236,7 @@ encoding._writeI64 = function _writeI64(dst, num, off, be) { */ encoding.writeU64 = function writeU64(dst, num, off) { - return encoding._writeI64(dst, num, off, false); + return write64(dst, num, off, false); }; /** @@ -411,7 +249,7 @@ encoding.writeU64 = function writeU64(dst, num, off) { */ encoding.writeU64BE = function writeU64BE(dst, num, off) { - return encoding._writeI64(dst, num, off, true); + return write64(dst, num, off, true); }; /** @@ -424,7 +262,7 @@ encoding.writeU64BE = function writeU64BE(dst, num, off) { */ encoding.writeI64 = function writeI64(dst, num, off) { - return encoding._writeI64(dst, num, off, false); + return write64(dst, num, off, false); }; /** @@ -437,7 +275,7 @@ encoding.writeI64 = function writeI64(dst, num, off) { */ encoding.writeI64BE = function writeI64BE(dst, num, off) { - return encoding._writeI64(dst, num, off, true); + return write64(dst, num, off, true); }; /** @@ -610,28 +448,6 @@ encoding.writeVarint = function writeVarint(dst, num, off) { return off; }; -/** - * Read a varint size. - * @param {Buffer} data - * @param {Number} off - * @returns {Number} - */ - -encoding.skipVarint = function skipVarint(data, off) { - assert(off < data.length, off); - - switch (data[off]) { - case 0xff: - return 9; - case 0xfe: - return 5; - case 0xfd: - return 3; - default: - return 1; - } -}; - /** * Calculate size of varint. * @param {Number} num @@ -772,27 +588,6 @@ encoding.writeVarint2 = function writeVarint2(dst, num, off) { return off; }; -/** - * Read a varint size. - * @param {Buffer} data - * @param {Number} off - * @returns {Number} - */ - -encoding.skipVarint2 = function skipVarint2(data, off) { - let size = 0; - - for (;;) { - assert(off < data.length, off); - const ch = data[off++]; - size++; - if ((ch & 0x80) === 0) - break; - } - - return size; -}; - /** * Calculate size of varint (type 2). * @param {Number} num @@ -986,9 +781,9 @@ encoding.sizeVarString = function sizeVarString(str, enc) { * @param {String} reason */ -encoding.EncodingError = function EncodingError(offset, reason) { +encoding.EncodingError = function EncodingError(offset, reason, start) { if (!(this instanceof EncodingError)) - return new EncodingError(offset, reason); + return new EncodingError(offset, reason, start); Error.call(this); @@ -996,7 +791,7 @@ encoding.EncodingError = function EncodingError(offset, reason) { this.message = `${reason} (offset=${offset}).`; if (Error.captureStackTrace) - Error.captureStackTrace(this, EncodingError); + Error.captureStackTrace(this, start || EncodingError); }; Object.setPrototypeOf(encoding.EncodingError.prototype, Error.prototype); @@ -1005,17 +800,58 @@ Object.setPrototypeOf(encoding.EncodingError.prototype, Error.prototype); * Helpers */ +function isSafe(hi, lo) { + if (hi < 0) { + hi = ~hi; + if (lo === 0) + hi += 1; + } + + return (hi & 0xffe00000) === 0; +} + +function write64(dst, num, off, be) { + let neg = false; + + if (num < 0) { + num = -num; + neg = true; + } + + let hi = (num * (1 / 0x100000000)) | 0; + let lo = num | 0; + + if (neg) { + if (lo === 0) { + hi = (~hi + 1) | 0; + } else { + hi = ~hi; + lo = ~lo + 1; + } + } + + if (be) { + off = dst.writeInt32BE(hi, off, true); + off = dst.writeInt32BE(lo, off, true); + } else { + off = dst.writeInt32LE(lo, off, true); + off = dst.writeInt32LE(hi, off, true); + } + + return off; +} + function Varint(size, value) { this.size = size; this.value = value; } -function enforce(value, offset, reason) { +function assert(value, offset) { if (!value) - throw new encoding.EncodingError(offset, reason); + throw new encoding.EncodingError(offset, 'Out of bounds read', assert); } -function assert(value, offset) { +function enforce(value, offset, reason) { if (!value) - throw new encoding.EncodingError(offset, 'Out of bounds read'); + throw new encoding.EncodingError(offset, reason, enforce); } diff --git a/lib/utils/reader.js b/lib/utils/reader.js index bbb825608..c2e75c7a7 100644 --- a/lib/utils/reader.js +++ b/lib/utils/reader.js @@ -42,7 +42,7 @@ function BufferReader(data, zeroCopy) { BufferReader.prototype.assert = function assert(value) { if (!value) - throw new encoding.EncodingError(this.offset, 'Out of bounds read'); + throw new encoding.EncodingError(this.offset, 'Out of bounds read', assert); }; /** @@ -53,7 +53,7 @@ BufferReader.prototype.assert = function assert(value) { BufferReader.prototype.enforce = function enforce(value, reason) { if (!value) - throw new encoding.EncodingError(this.offset, reason); + throw new encoding.EncodingError(this.offset, reason, enforce); }; /** @@ -237,32 +237,6 @@ BufferReader.prototype.readU64BE = function readU64BE() { return ret; }; -/** - * Read first least significant 53 bits of - * a uint64le as a js number. Maintain the sign. - * @returns {Number} - */ - -BufferReader.prototype.readU53 = function readU53() { - this.assert(this.offset + 8 <= this.data.length); - const ret = encoding.readU53(this.data, this.offset); - this.offset += 8; - return ret; -}; - -/** - * Read first least significant 53 bits of - * a uint64be as a js number. Maintain the sign. - * @returns {Number} - */ - -BufferReader.prototype.readU53BE = function readU53BE() { - this.assert(this.offset + 8 <= this.data.length); - const ret = encoding.readU53BE(this.data, this.offset); - this.offset += 8; - return ret; -}; - /** * Read int8. * @returns {Number} @@ -349,32 +323,6 @@ BufferReader.prototype.readI64BE = function readI64BE() { return ret; }; -/** - * Read first least significant 53 bits of - * a int64le as a js number. Maintain the sign. - * @returns {Number} - */ - -BufferReader.prototype.readI53 = function readI53() { - this.assert(this.offset + 8 <= this.data.length); - const ret = encoding.readI53(this.data, this.offset); - this.offset += 8; - return ret; -}; - -/** - * Read first least significant 53 bits of - * a int64be as a js number. Maintain the sign. - * @returns {Number} - */ - -BufferReader.prototype.readI53BE = function readI53BE() { - this.assert(this.offset + 8 <= this.data.length); - const ret = encoding.readI53BE(this.data, this.offset); - this.offset += 8; - return ret; -}; - /** * Read uint64le. * @returns {U64} @@ -482,18 +430,6 @@ BufferReader.prototype.readVarint = function readVarint() { return value; }; -/** - * Skip past a varint. - * @returns {Number} - */ - -BufferReader.prototype.skipVarint = function skipVarint() { - const size = encoding.skipVarint(this.data, this.offset); - this.assert(this.offset + size <= this.data.length); - this.offset += size; - return size; -}; - /** * Read a varint. * @returns {U64} @@ -516,17 +452,6 @@ BufferReader.prototype.readVarint2 = function readVarint2() { return value; }; -/** - * Skip past a varint (type 2). - * @returns {Number} - */ - -BufferReader.prototype.skipVarint2 = function skipVarint2() { - const size = encoding.skipVarint2(this.data, this.offset); - this.assert(this.offset + size <= this.data.length); - this.offset += size; -}; - /** * Read a varint (type 2). * @returns {U64} diff --git a/lib/wallet/masterkey.js b/lib/wallet/masterkey.js index 373da1a74..96083e113 100644 --- a/lib/wallet/masterkey.js +++ b/lib/wallet/masterkey.js @@ -594,7 +594,7 @@ MasterKey.prototype.fromRaw = function fromRaw(raw) { } // NOTE: useless varint - br.skipVarint(); + br.readVarint(); this.key = HD.PrivateKey.fromRaw(br.readBytes(82)); diff --git a/lib/wallet/txdb.js b/lib/wallet/txdb.js index b7367af53..079480e4c 100644 --- a/lib/wallet/txdb.js +++ b/lib/wallet/txdb.js @@ -2669,10 +2669,10 @@ TXDBState.prototype.toRaw = function toRaw() { TXDBState.prototype.fromRaw = function fromRaw(data) { const br = new BufferReader(data); - this.tx = br.readU53(); - this.coin = br.readU53(); - this.unconfirmed = br.readU53(); - this.confirmed = br.readU53(); + this.tx = br.readU64(); + this.coin = br.readU64(); + this.unconfirmed = br.readU64(); + this.confirmed = br.readU64(); return this; }; diff --git a/migrate/coins-old.js b/migrate/coins-old.js index 6eda01198..4b8148627 100644 --- a/migrate/coins-old.js +++ b/migrate/coins-old.js @@ -600,7 +600,7 @@ function skipCoin(br) { } // Skip past the value. - br.skipVarint(); + br.readVarint(); return br.offset - start; } diff --git a/migrate/coins/compress.js b/migrate/coins/compress.js index 455780c1e..311a5386b 100644 --- a/migrate/coins/compress.js +++ b/migrate/coins/compress.js @@ -218,7 +218,7 @@ function skipOutput(br) { const start = br.offset; // Skip past the value. - br.skipVarint(); + br.readVarint(); // Skip past the compressed scripts. switch (br.readU8()) {