675 lines
15 KiB
JavaScript
675 lines
15 KiB
JavaScript
/*!
|
|
* reader.js - buffer reader for bcoin
|
|
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
|
|
* Copyright (c) 2014-2017, Christopher Jeffrey (MIT License).
|
|
* https://github.com/bcoin-org/bcoin
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
const assert = require('assert');
|
|
const encoding = require('./encoding');
|
|
const digest = require('../crypto/digest');
|
|
|
|
/**
|
|
* An object that allows reading of buffers in a sane manner.
|
|
* @alias module:utils.BufferReader
|
|
* @constructor
|
|
* @param {Buffer} data
|
|
* @param {Boolean?} zeroCopy - Do not reallocate buffers when
|
|
* slicing. Note that this can lead to memory leaks if not used
|
|
* carefully.
|
|
*/
|
|
|
|
function BufferReader(data, zeroCopy) {
|
|
if (!(this instanceof BufferReader))
|
|
return new BufferReader(data, zeroCopy);
|
|
|
|
assert(Buffer.isBuffer(data), 'Must pass a Buffer.');
|
|
|
|
this.data = data;
|
|
this.offset = 0;
|
|
this.zeroCopy = zeroCopy || false;
|
|
this.stack = [];
|
|
}
|
|
|
|
/**
|
|
* Assertion.
|
|
* @param {Boolean} value
|
|
*/
|
|
|
|
BufferReader.prototype.assert = function assert(value) {
|
|
if (!value)
|
|
throw new encoding.EncodingError(this.offset, 'Out of bounds read');
|
|
};
|
|
|
|
/**
|
|
* Assertion.
|
|
* @param {Boolean} value
|
|
* @param {String} reason
|
|
*/
|
|
|
|
BufferReader.prototype.enforce = function enforce(value, reason) {
|
|
if (!value)
|
|
throw new encoding.EncodingError(this.offset, reason);
|
|
};
|
|
|
|
/**
|
|
* Get total size of passed-in Buffer.
|
|
* @returns {Buffer}
|
|
*/
|
|
|
|
BufferReader.prototype.getSize = function getSize() {
|
|
return this.data.length;
|
|
};
|
|
|
|
/**
|
|
* Calculate number of bytes left to read.
|
|
* @returns {Number}
|
|
*/
|
|
|
|
BufferReader.prototype.left = function left() {
|
|
this.assert(this.offset <= this.data.length);
|
|
return this.data.length - this.offset;
|
|
};
|
|
|
|
/**
|
|
* Seek to a position to read from by offset.
|
|
* @param {Number} off - Offset (positive or negative).
|
|
*/
|
|
|
|
BufferReader.prototype.seek = function seek(off) {
|
|
this.assert(this.offset + off >= 0);
|
|
this.assert(this.offset + off <= this.data.length);
|
|
this.offset += off;
|
|
return off;
|
|
};
|
|
|
|
/**
|
|
* Mark the current starting position.
|
|
*/
|
|
|
|
BufferReader.prototype.start = function start() {
|
|
this.stack.push(this.offset);
|
|
return this.offset;
|
|
};
|
|
|
|
/**
|
|
* Stop reading. Pop the start position off the stack
|
|
* and calculate the size of the data read.
|
|
* @returns {Number} Size.
|
|
* @throws on empty stack.
|
|
*/
|
|
|
|
BufferReader.prototype.end = function end() {
|
|
assert(this.stack.length > 0);
|
|
|
|
const start = this.stack.pop();
|
|
|
|
return this.offset - start;
|
|
};
|
|
|
|
/**
|
|
* Stop reading. Pop the start position off the stack
|
|
* and return the data read.
|
|
* @param {Bolean?} zeroCopy - Do a fast buffer
|
|
* slice instead of allocating a new buffer (warning:
|
|
* may cause memory leaks if not used with care).
|
|
* @returns {Buffer} Data read.
|
|
* @throws on empty stack.
|
|
*/
|
|
|
|
BufferReader.prototype.endData = function endData(zeroCopy) {
|
|
assert(this.stack.length > 0);
|
|
|
|
const start = this.stack.pop();
|
|
const end = this.offset;
|
|
const size = end - start;
|
|
const data = this.data;
|
|
|
|
if (size === data.length)
|
|
return data;
|
|
|
|
if (this.zeroCopy || zeroCopy)
|
|
return data.slice(start, end);
|
|
|
|
const ret = Buffer.allocUnsafe(size);
|
|
data.copy(ret, 0, start, end);
|
|
|
|
return ret;
|
|
};
|
|
|
|
/**
|
|
* Destroy the reader. Remove references to the data.
|
|
*/
|
|
|
|
BufferReader.prototype.destroy = function destroy() {
|
|
this.offset = null;
|
|
this.stack = null;
|
|
this.data = null;
|
|
};
|
|
|
|
/**
|
|
* Read uint8.
|
|
* @returns {Number}
|
|
*/
|
|
|
|
BufferReader.prototype.readU8 = function readU8() {
|
|
this.assert(this.offset + 1 <= this.data.length);
|
|
const ret = this.data[this.offset];
|
|
this.offset += 1;
|
|
return ret;
|
|
};
|
|
|
|
/**
|
|
* Read uint16le.
|
|
* @returns {Number}
|
|
*/
|
|
|
|
BufferReader.prototype.readU16 = function readU16() {
|
|
this.assert(this.offset + 2 <= this.data.length);
|
|
const ret = this.data.readUInt16LE(this.offset, true);
|
|
this.offset += 2;
|
|
return ret;
|
|
};
|
|
|
|
/**
|
|
* Read uint16be.
|
|
* @returns {Number}
|
|
*/
|
|
|
|
BufferReader.prototype.readU16BE = function readU16BE() {
|
|
this.assert(this.offset + 2 <= this.data.length);
|
|
const ret = this.data.readUInt16BE(this.offset, true);
|
|
this.offset += 2;
|
|
return ret;
|
|
};
|
|
|
|
/**
|
|
* Read uint32le.
|
|
* @returns {Number}
|
|
*/
|
|
|
|
BufferReader.prototype.readU32 = function readU32() {
|
|
this.assert(this.offset + 4 <= this.data.length);
|
|
const ret = this.data.readUInt32LE(this.offset, true);
|
|
this.offset += 4;
|
|
return ret;
|
|
};
|
|
|
|
/**
|
|
* Read uint32be.
|
|
* @returns {Number}
|
|
*/
|
|
|
|
BufferReader.prototype.readU32BE = function readU32BE() {
|
|
this.assert(this.offset + 4 <= this.data.length);
|
|
const ret = this.data.readUInt32BE(this.offset, true);
|
|
this.offset += 4;
|
|
return ret;
|
|
};
|
|
|
|
/**
|
|
* Read uint64le as a js number.
|
|
* @returns {Number}
|
|
* @throws on num > MAX_SAFE_INTEGER
|
|
*/
|
|
|
|
BufferReader.prototype.readU64 = function readU64() {
|
|
this.assert(this.offset + 8 <= this.data.length);
|
|
const ret = encoding.readU64(this.data, this.offset);
|
|
this.offset += 8;
|
|
return ret;
|
|
};
|
|
|
|
/**
|
|
* Read uint64be as a js number.
|
|
* @returns {Number}
|
|
* @throws on num > MAX_SAFE_INTEGER
|
|
*/
|
|
|
|
BufferReader.prototype.readU64BE = function readU64BE() {
|
|
this.assert(this.offset + 8 <= this.data.length);
|
|
const ret = encoding.readU64BE(this.data, this.offset);
|
|
this.offset += 8;
|
|
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}
|
|
*/
|
|
|
|
BufferReader.prototype.readI8 = function readI8() {
|
|
this.assert(this.offset + 1 <= this.data.length);
|
|
const ret = this.data.readInt8(this.offset, true);
|
|
this.offset += 1;
|
|
return ret;
|
|
};
|
|
|
|
/**
|
|
* Read int16le.
|
|
* @returns {Number}
|
|
*/
|
|
|
|
BufferReader.prototype.readI16 = function readI16() {
|
|
this.assert(this.offset + 2 <= this.data.length);
|
|
const ret = this.data.readInt16LE(this.offset, true);
|
|
this.offset += 2;
|
|
return ret;
|
|
};
|
|
|
|
/**
|
|
* Read int16be.
|
|
* @returns {Number}
|
|
*/
|
|
|
|
BufferReader.prototype.readI16BE = function readI16BE() {
|
|
this.assert(this.offset + 2 <= this.data.length);
|
|
const ret = this.data.readInt16BE(this.offset, true);
|
|
this.offset += 2;
|
|
return ret;
|
|
};
|
|
|
|
/**
|
|
* Read int32le.
|
|
* @returns {Number}
|
|
*/
|
|
|
|
BufferReader.prototype.readI32 = function readI32() {
|
|
this.assert(this.offset + 4 <= this.data.length);
|
|
const ret = this.data.readInt32LE(this.offset, true);
|
|
this.offset += 4;
|
|
return ret;
|
|
};
|
|
|
|
/**
|
|
* Read int32be.
|
|
* @returns {Number}
|
|
*/
|
|
|
|
BufferReader.prototype.readI32BE = function readI32BE() {
|
|
this.assert(this.offset + 4 <= this.data.length);
|
|
const ret = this.data.readInt32BE(this.offset, true);
|
|
this.offset += 4;
|
|
return ret;
|
|
};
|
|
|
|
/**
|
|
* Read int64le as a js number.
|
|
* @returns {Number}
|
|
* @throws on num > MAX_SAFE_INTEGER
|
|
*/
|
|
|
|
BufferReader.prototype.readI64 = function readI64() {
|
|
this.assert(this.offset + 8 <= this.data.length);
|
|
const ret = encoding.readI64(this.data, this.offset);
|
|
this.offset += 8;
|
|
return ret;
|
|
};
|
|
|
|
/**
|
|
* Read int64be as a js number.
|
|
* @returns {Number}
|
|
* @throws on num > MAX_SAFE_INTEGER
|
|
*/
|
|
|
|
BufferReader.prototype.readI64BE = function readI64BE() {
|
|
this.assert(this.offset + 8 <= this.data.length);
|
|
const ret = encoding.readI64BE(this.data, this.offset);
|
|
this.offset += 8;
|
|
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}
|
|
*/
|
|
|
|
BufferReader.prototype.readU64N = function readU64N() {
|
|
this.assert(this.offset + 8 <= this.data.length);
|
|
const ret = encoding.readU64N(this.data, this.offset);
|
|
this.offset += 8;
|
|
return ret;
|
|
};
|
|
|
|
/**
|
|
* Read uint64be.
|
|
* @returns {U64}
|
|
*/
|
|
|
|
BufferReader.prototype.readU64BEN = function readU64BEN() {
|
|
this.assert(this.offset + 8 <= this.data.length);
|
|
const ret = encoding.readU64BEN(this.data, this.offset);
|
|
this.offset += 8;
|
|
return ret;
|
|
};
|
|
|
|
/**
|
|
* Read int64le.
|
|
* @returns {I64}
|
|
*/
|
|
|
|
BufferReader.prototype.readI64N = function readI64N() {
|
|
this.assert(this.offset + 8 <= this.data.length);
|
|
const ret = encoding.readI64N(this.data, this.offset);
|
|
this.offset += 8;
|
|
return ret;
|
|
};
|
|
|
|
/**
|
|
* Read int64be.
|
|
* @returns {I64}
|
|
*/
|
|
|
|
BufferReader.prototype.readI64BEN = function readI64BEN() {
|
|
this.assert(this.offset + 8 <= this.data.length);
|
|
const ret = encoding.readI64BEN(this.data, this.offset);
|
|
this.offset += 8;
|
|
return ret;
|
|
};
|
|
|
|
/**
|
|
* Read float le.
|
|
* @returns {Number}
|
|
*/
|
|
|
|
BufferReader.prototype.readFloat = function readFloat() {
|
|
this.assert(this.offset + 4 <= this.data.length);
|
|
const ret = this.data.readFloatLE(this.offset, true);
|
|
this.offset += 4;
|
|
return ret;
|
|
};
|
|
|
|
/**
|
|
* Read float be.
|
|
* @returns {Number}
|
|
*/
|
|
|
|
BufferReader.prototype.readFloatBE = function readFloatBE() {
|
|
this.assert(this.offset + 4 <= this.data.length);
|
|
const ret = this.data.readFloatBE(this.offset, true);
|
|
this.offset += 4;
|
|
return ret;
|
|
};
|
|
|
|
/**
|
|
* Read double float le.
|
|
* @returns {Number}
|
|
*/
|
|
|
|
BufferReader.prototype.readDouble = function readDouble() {
|
|
this.assert(this.offset + 8 <= this.data.length);
|
|
const ret = this.data.readDoubleLE(this.offset, true);
|
|
this.offset += 8;
|
|
return ret;
|
|
};
|
|
|
|
/**
|
|
* Read double float be.
|
|
* @returns {Number}
|
|
*/
|
|
|
|
BufferReader.prototype.readDoubleBE = function readDoubleBE() {
|
|
this.assert(this.offset + 8 <= this.data.length);
|
|
const ret = this.data.readDoubleBE(this.offset, true);
|
|
this.offset += 8;
|
|
return ret;
|
|
};
|
|
|
|
/**
|
|
* Read a varint.
|
|
* @returns {Number}
|
|
*/
|
|
|
|
BufferReader.prototype.readVarint = function readVarint() {
|
|
const {size, value} = encoding.readVarint(this.data, this.offset);
|
|
this.offset += size;
|
|
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}
|
|
*/
|
|
|
|
BufferReader.prototype.readVarintN = function readVarintN() {
|
|
const {size, value} = encoding.readVarintN(this.data, this.offset);
|
|
this.offset += size;
|
|
return value;
|
|
};
|
|
|
|
/**
|
|
* Read a varint (type 2).
|
|
* @returns {Number}
|
|
*/
|
|
|
|
BufferReader.prototype.readVarint2 = function readVarint2() {
|
|
const {size, value} = encoding.readVarint2(this.data, this.offset);
|
|
this.offset += size;
|
|
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}
|
|
*/
|
|
|
|
BufferReader.prototype.readVarint2N = function readVarint2N() {
|
|
const {size, value} = encoding.readVarint2N(this.data, this.offset);
|
|
this.offset += size;
|
|
return value;
|
|
};
|
|
|
|
/**
|
|
* Read N bytes (will do a fast slice if zero copy).
|
|
* @param {Number} size
|
|
* @param {Bolean?} zeroCopy - Do a fast buffer
|
|
* slice instead of allocating a new buffer (warning:
|
|
* may cause memory leaks if not used with care).
|
|
* @returns {Buffer}
|
|
*/
|
|
|
|
BufferReader.prototype.readBytes = function readBytes(size, zeroCopy) {
|
|
assert(size >= 0);
|
|
this.assert(this.offset + size <= this.data.length);
|
|
|
|
let ret;
|
|
if (this.zeroCopy || zeroCopy) {
|
|
ret = this.data.slice(this.offset, this.offset + size);
|
|
} else {
|
|
ret = Buffer.allocUnsafe(size);
|
|
this.data.copy(ret, 0, this.offset, this.offset + size);
|
|
}
|
|
|
|
this.offset += size;
|
|
|
|
return ret;
|
|
};
|
|
|
|
/**
|
|
* Read a varint number of bytes (will do a fast slice if zero copy).
|
|
* @param {Bolean?} zeroCopy - Do a fast buffer
|
|
* slice instead of allocating a new buffer (warning:
|
|
* may cause memory leaks if not used with care).
|
|
* @returns {Buffer}
|
|
*/
|
|
|
|
BufferReader.prototype.readVarBytes = function readVarBytes(zeroCopy) {
|
|
return this.readBytes(this.readVarint(), zeroCopy);
|
|
};
|
|
|
|
/**
|
|
* Read a string.
|
|
* @param {String} enc - Any buffer-supported encoding.
|
|
* @param {Number} size
|
|
* @returns {String}
|
|
*/
|
|
|
|
BufferReader.prototype.readString = function readString(enc, size) {
|
|
assert(size >= 0);
|
|
this.assert(this.offset + size <= this.data.length);
|
|
const ret = this.data.toString(enc, this.offset, this.offset + size);
|
|
this.offset += size;
|
|
return ret;
|
|
};
|
|
|
|
/**
|
|
* Read a 32-byte hash.
|
|
* @param {String} enc - `"hex"` or `null`.
|
|
* @returns {Hash|Buffer}
|
|
*/
|
|
|
|
BufferReader.prototype.readHash = function readHash(enc) {
|
|
if (enc)
|
|
return this.readString(enc, 32);
|
|
return this.readBytes(32);
|
|
};
|
|
|
|
/**
|
|
* Read string of a varint length.
|
|
* @param {String} enc - Any buffer-supported encoding.
|
|
* @param {Number?} limit - Size limit.
|
|
* @returns {String}
|
|
*/
|
|
|
|
BufferReader.prototype.readVarString = function readVarString(enc, limit) {
|
|
const size = this.readVarint();
|
|
this.enforce(!limit || size <= limit, 'String exceeds limit.');
|
|
return this.readString(enc, size);
|
|
};
|
|
|
|
/**
|
|
* Read a null-terminated string.
|
|
* @param {String} enc - Any buffer-supported encoding.
|
|
* @returns {String}
|
|
*/
|
|
|
|
BufferReader.prototype.readNullString = function readNullString(enc) {
|
|
this.assert(this.offset + 1 <= this.data.length);
|
|
|
|
let i = this.offset;
|
|
for (; i < this.data.length; i++) {
|
|
if (this.data[i] === 0)
|
|
break;
|
|
}
|
|
|
|
this.assert(i !== this.data.length);
|
|
|
|
const ret = this.readString(enc, i - this.offset);
|
|
|
|
this.offset = i + 1;
|
|
|
|
return ret;
|
|
};
|
|
|
|
/**
|
|
* Create a checksum from the last start position.
|
|
* @returns {Number} Checksum.
|
|
*/
|
|
|
|
BufferReader.prototype.createChecksum = function createChecksum() {
|
|
let start = 0;
|
|
|
|
if (this.stack.length > 0)
|
|
start = this.stack[this.stack.length - 1];
|
|
|
|
const data = this.data.slice(start, this.offset);
|
|
|
|
return digest.hash256(data).readUInt32LE(0, true);
|
|
};
|
|
|
|
/**
|
|
* Verify a 4-byte checksum against a calculated checksum.
|
|
* @returns {Number} checksum
|
|
* @throws on bad checksum
|
|
*/
|
|
|
|
BufferReader.prototype.verifyChecksum = function verifyChecksum() {
|
|
const chk = this.createChecksum();
|
|
const checksum = this.readU32();
|
|
this.enforce(chk === checksum, 'Checksum mismatch.');
|
|
return checksum;
|
|
};
|
|
|
|
/*
|
|
* Expose
|
|
*/
|
|
|
|
module.exports = BufferReader;
|