diff --git a/lib/bcoin/reader.js b/lib/bcoin/reader.js index d0c1cc07..c6ab21b6 100644 --- a/lib/bcoin/reader.js +++ b/lib/bcoin/reader.js @@ -472,6 +472,18 @@ BufferReader.prototype.readVarint = function readVarint(big) { return result.value; }; +/** + * Read a varint (type 2). + * @param {Boolean?} big - Whether to read as a big number. + * @returns {Number} + */ + +BufferReader.prototype.readVarint2 = function readVarint2(big) { + var result = utils.readVarint2(this.data, this.offset, big); + this.offset += result.size; + return result.value; +}; + /** * Read N bytes (will do a fast slice if zero copy). * @param {Number} size diff --git a/lib/bcoin/utils.js b/lib/bcoin/utils.js index c1173dd7..6850aa09 100644 --- a/lib/bcoin/utils.js +++ b/lib/bcoin/utils.js @@ -1694,7 +1694,7 @@ utils.write64BE = function write64BE(dst, num, off) { * @param {Buffer} data * @param {Number} off * @param {Boolean?} big - Whether to read as a big number. - * @returns {Number} + * @returns {Object} */ utils.readVarint = function readVarint(data, off, big) { @@ -1814,6 +1814,147 @@ utils.sizeVarint = function sizeVarint(num) { return 9; }; +/** + * Read a varint (type 2). + * @param {Buffer} data + * @param {Number} off + * @param {Boolean?} big - Whether to read as a big number. + * @returns {Object} + */ + +utils.readVarint2 = function readVarint2(data, off, big) { + var num = 0; + var size = 0; + var bnum, ch; + + off = off >>> 0; + + for (;;) { + assert(off < data.length); + + ch = data[off++]; + size++; + + if (num >= 0x3fffffffffff) { + assert(big, 'Number exceeds 2^53-1.'); + bnum = new bn(num); + } + + if (bnum) { + bnum.iushln(7).iaddn(ch & 0x7f); + if ((ch & 0x80) === 0) + break; + bnum.iaddn(1); + continue; + } + + num = (num * 0x80) + (ch & 0x7f); + if ((ch & 0x80) === 0) + break; + num++; + } + + if (bnum) + return { size: size, value: bnum }; + + if (big) + num = new bn(num); + + return { size: size, value: num }; +}; + +/** + * Write a varint (type 2). + * @param {Buffer} dst + * @param {BN|Number} num + * @param {Number} off + * @returns {Number} Number of bytes written. + */ + +utils.writeVarint2 = function writeVarint2(dst, num, off) { + var tmp = []; + var len = 0; + + if (bn.isBN(num)) { + if (num.bitLength() > 53) { + for (;;) { + tmp[len] = (num.words[0] & 0x7f) | (len ? 0x80 : 0x00); + if (num.cmpn(0x7f) <= 0) + break; + num.iushrn(7).isubn(1); + len++; + } + + assert(off + len <= dst.length); + + do { + dst[off++] = tmp[len]; + } while (len--); + + return off; + } + + num = num.toNumber(); + } + + off = off >>> 0; + num = +num; + + for (;;) { + tmp[len] = (num & 0x7f) | (len ? 0x80 : 0x00); + if (num <= 0x7f) + break; + num = ((num - (num % 0x80)) / 0x80) - 1; + len++; + } + + assert(off + len <= dst.length); + + do { + dst[off++] = tmp[len]; + } while (len--); + + return off; +}; + +/** + * Calculate size of varint (type 2). + * @param {BN|Number} num + * @returns {Number} size + */ + +utils.sizeVarint2 = function sizeVarint2(num) { + var size = 0; + + if (bn.isBN(num)) { + if (num.bitLength() > 53) { + num = num.clone(); + + for (;;) { + size++; + if (num.cmpn(0x7f) <= 0) + break; + num.iushrn(7).isubn(1); + } + + return size; + } + + num = num.toNumber(); + } + + num = +num; + + for (;;) { + size++; + if (num <= 0x7f) + break; + num = ((num - (num % 0x80)) / 0x80) - 1; + } + + return size; +}; + /** * Buffer comparator (memcmp + length comparison). * @param {Buffer} a diff --git a/lib/bcoin/writer.js b/lib/bcoin/writer.js index 50e16721..9bb6bc1c 100644 --- a/lib/bcoin/writer.js +++ b/lib/bcoin/writer.js @@ -32,10 +32,11 @@ var FLBE = 16; var DBL = 17; var DBLBE = 18; var VARINT = 19; -var BYTES = 20; -var STR = 21; -var CHECKSUM = 22; -var FILL = 23; +var VARINT2 = 20; +var BYTES = 21; +var STR = 22; +var CHECKSUM = 23; +var FILL = 24; /** * An object that allows writing of buffers in a @@ -97,6 +98,7 @@ BufferWriter.prototype.render = function render(keep) { case DBL: off = data.writeDoubleLE(item[1], off, true); break; case DBLBE: off = data.writeDoubleBE(item[1], off, true); break; case VARINT: off = utils.writeVarint(data, item[1], off); break; + case VARINT2: off = utils.writeVarint2(data, item[1], off); break; case BYTES: off += item[1].copy(data, off); break; case STR: off += data.write(item[1], off, item[2]); break; case CHECKSUM: @@ -343,6 +345,21 @@ BufferWriter.prototype.writeVarint = function writeVarint(value) { this.data.push([VARINT, value]); }; +/** + * Write a varint (type 2). + * @param {BN|Number} value + */ + +BufferWriter.prototype.writeVarint2 = function writeVarint2(value) { + if (typeof value === 'number') + assert(value >= 0); + else + assert(!value.isNeg()); + + this.written += utils.sizeVarint2(value); + this.data.push([VARINT2, value]); +}; + /** * Write bytes. * @param {Buffer} value diff --git a/test/utils-test.js b/test/utils-test.js index 523c85eb..f525b9e5 100644 --- a/test/utils-test.js +++ b/test/utils-test.js @@ -61,6 +61,62 @@ describe('Utils', function() { }); }); + it('should write/read new varints', function() { + /* + * 0: [0x00] 256: [0x81 0x00] + * 1: [0x01] 16383: [0xFE 0x7F] + * 127: [0x7F] 16384: [0xFF 0x00] + * 128: [0x80 0x00] 16511: [0x80 0xFF 0x7F] + * 255: [0x80 0x7F] 65535: [0x82 0xFD 0x7F] + * 2^32: [0x8E 0xFE 0xFE 0xFF 0x00] + */ + + var n = 0; + var b = new Buffer(1); + b.fill(0x00); + utils.writeVarint2(b, 0, 0); + assert.equal(utils.readVarint2(b, 0).value, 0); + assert.deepEqual(b, [0]); + + var b = new Buffer(1); + b.fill(0x00); + utils.writeVarint2(b, 1, 0); + assert.equal(utils.readVarint2(b, 0).value, 1); + assert.deepEqual(b, [1]); + + var b = new Buffer(1); + b.fill(0x00); + utils.writeVarint2(b, 127, 0); + assert.equal(utils.readVarint2(b, 0).value, 127); + assert.deepEqual(b, [0x7f]); + + var b = new Buffer(2); + b.fill(0x00); + utils.writeVarint2(b, 128, 0); + assert.equal(utils.readVarint2(b, 0).value, 128); + assert.deepEqual(b, [0x80, 0x00]); + + var b = new Buffer(2); + b.fill(0x00); + utils.writeVarint2(b, 255, 0); + assert.equal(utils.readVarint2(b, 0).value, 255); + assert.deepEqual(b, [0x80, 0x7f]); + + var b = new Buffer(3); + b.fill(0x00); + utils.writeVarint2(b, 16511, 0); + assert.equal(utils.readVarint2(b, 0).value, 16511); + //assert.deepEqual(b, [0x80, 0xff, 0x7f]); + assert.deepEqual(b, [0xff, 0x7f, 0x00]); + + var b = new Buffer(3); + b.fill(0x00); + utils.writeVarint2(b, 65535, 0); + assert.equal(utils.readVarint2(b, 0).value, 65535); + //assert.deepEqual(b, [0x82, 0xfd, 0x7f]); + assert.deepEqual(b, [0x82, 0xfe, 0x7f]); + }); + var unsigned = [ new bn('ffeeffee'), new bn('001fffeeffeeffee'),