/*!
* 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';
var assert = require('assert');
var encoding = require('./encoding');
var crypto = require('../crypto/crypto');
/**
* 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 = [];
}
/**
* 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() {
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) {
assert(this.offset + off >= 0);
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() {
var start, end;
assert(this.stack.length > 0);
start = this.stack.pop();
end = this.offset;
return end - 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) {
var ret, start, end, size, data;
assert(this.stack.length > 0);
start = this.stack.pop();
end = this.offset;
size = end - start;
data = this.data;
if (size === data.length)
return data;
if (this.zeroCopy || zeroCopy)
return data.slice(start, end);
ret = new Buffer(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() {
var ret;
assert(this.offset + 1 <= this.data.length);
ret = this.data[this.offset];
this.offset += 1;
return ret;
};
/**
* Read uint16le.
* @returns {Number}
*/
BufferReader.prototype.readU16 = function readU16() {
var ret;
assert(this.offset + 2 <= this.data.length);
ret = this.data.readUInt16LE(this.offset, true);
this.offset += 2;
return ret;
};
/**
* Read uint16be.
* @returns {Number}
*/
BufferReader.prototype.readU16BE = function readU16BE() {
var ret;
assert(this.offset + 2 <= this.data.length);
ret = this.data.readUInt16BE(this.offset, true);
this.offset += 2;
return ret;
};
/**
* Read uint32le.
* @returns {Number}
*/
BufferReader.prototype.readU32 = function readU32() {
var ret;
assert(this.offset + 4 <= this.data.length);
ret = this.data.readUInt32LE(this.offset, true);
this.offset += 4;
return ret;
};
/**
* Read uint32be.
* @returns {Number}
*/
BufferReader.prototype.readU32BE = function readU32BE() {
var ret;
assert(this.offset + 4 <= this.data.length);
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() {
var ret;
assert(this.offset + 8 <= this.data.length);
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() {
var ret;
assert(this.offset + 8 <= this.data.length);
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() {
var ret;
assert(this.offset + 8 <= this.data.length);
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() {
var ret;
assert(this.offset + 8 <= this.data.length);
ret = encoding.readU53BE(this.data, this.offset);
this.offset += 8;
return ret;
};
/**
* Read int8.
* @returns {Number}
*/
BufferReader.prototype.read8 = function read8() {
var ret;
assert(this.offset + 1 <= this.data.length);
ret = this.data.readInt8(this.offset, true);
this.offset += 1;
return ret;
};
/**
* Read int16le.
* @returns {Number}
*/
BufferReader.prototype.read16 = function read16() {
var ret;
assert(this.offset + 2 <= this.data.length);
ret = this.data.readInt16LE(this.offset, true);
this.offset += 2;
return ret;
};
/**
* Read int16be.
* @returns {Number}
*/
BufferReader.prototype.read16BE = function read16BE() {
var ret;
assert(this.offset + 2 <= this.data.length);
ret = this.data.readInt16BE(this.offset, true);
this.offset += 2;
return ret;
};
/**
* Read int32le.
* @returns {Number}
*/
BufferReader.prototype.read32 = function read32() {
var ret;
assert(this.offset + 4 <= this.data.length);
ret = this.data.readInt32LE(this.offset, true);
this.offset += 4;
return ret;
};
/**
* Read int32be.
* @returns {Number}
*/
BufferReader.prototype.read32BE = function read32BE() {
var ret;
assert(this.offset + 4 <= this.data.length);
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.read64 = function read64() {
var ret;
assert(this.offset + 8 <= this.data.length);
ret = encoding.read64(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.read64BE = function read64BE() {
var ret;
assert(this.offset + 8 <= this.data.length);
ret = encoding.read64BE(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.read53 = function read53() {
var ret;
assert(this.offset + 8 <= this.data.length);
ret = encoding.read53(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.read53BE = function read53BE() {
var ret;
assert(this.offset + 8 <= this.data.length);
ret = encoding.read53BE(this.data, this.offset);
this.offset += 8;
return ret;
};
/**
* Read uint64le.
* @returns {BN}
*/
BufferReader.prototype.readU64BN = function readU64BN() {
var ret;
assert(this.offset + 8 <= this.data.length);
ret = encoding.readU64BN(this.data, this.offset);
this.offset += 8;
return ret;
};
/**
* Read uint64be.
* @returns {BN}
*/
BufferReader.prototype.readU64BEBN = function readU64BEBN() {
var ret;
assert(this.offset + 8 <= this.data.length);
ret = encoding.readU64BEBN(this.data, this.offset);
this.offset += 8;
return ret;
};
/**
* Read int64le.
* @returns {BN}
*/
BufferReader.prototype.read64BN = function read64BN() {
var ret;
assert(this.offset + 8 <= this.data.length);
ret = encoding.read64BN(this.data, this.offset);
this.offset += 8;
return ret;
};
/**
* Read int64be.
* @returns {BN}
*/
BufferReader.prototype.read64BEBN = function read64BEBN() {
var ret;
assert(this.offset + 8 <= this.data.length);
ret = encoding.read64BEBN(this.data, this.offset);
this.offset += 8;
return ret;
};
/**
* Read float le.
* @returns {Number}
*/
BufferReader.prototype.readFloat = function readFloat() {
var ret;
assert(this.offset + 4 <= this.data.length);
ret = this.data.readFloatLE(this.offset, true);
this.offset += 4;
return ret;
};
/**
* Read float be.
* @returns {Number}
*/
BufferReader.prototype.readFloatBE = function readFloatBE() {
var ret;
assert(this.offset + 4 <= this.data.length);
ret = this.data.readFloatBE(this.offset, true);
this.offset += 4;
return ret;
};
/**
* Read double float le.
* @returns {Number}
*/
BufferReader.prototype.readDouble = function readDouble() {
var ret;
assert(this.offset + 8 <= this.data.length);
ret = this.data.readDoubleLE(this.offset, true);
this.offset += 8;
return ret;
};
/**
* Read double float be.
* @returns {Number}
*/
BufferReader.prototype.readDoubleBE = function readDoubleBE() {
var ret;
assert(this.offset + 8 <= this.data.length);
ret = this.data.readDoubleBE(this.offset, true);
this.offset += 8;
return ret;
};
/**
* Read a varint.
* @returns {Number}
*/
BufferReader.prototype.readVarint = function readVarint() {
var result = encoding.readVarint(this.data, this.offset);
this.offset += result.size;
return result.value;
};
/**
* Skip past a varint.
* @returns {Number}
*/
BufferReader.prototype.skipVarint = function skipVarint() {
var size = encoding.skipVarint(this.data, this.offset);
assert(this.offset + size <= this.data.length);
this.offset += size;
};
/**
* Read a varint.
* @returns {BN}
*/
BufferReader.prototype.readVarintBN = function readVarintBN() {
var result = encoding.readVarintBN(this.data, this.offset);
this.offset += result.size;
return result.value;
};
/**
* Read a varint (type 2).
* @returns {Number}
*/
BufferReader.prototype.readVarint2 = function readVarint2() {
var result = encoding.readVarint2(this.data, this.offset);
this.offset += result.size;
return result.value;
};
/**
* Skip past a varint (type 2).
* @returns {Number}
*/
BufferReader.prototype.skipVarint2 = function skipVarint2() {
var size = encoding.skipVarint2(this.data, this.offset);
assert(this.offset + size <= this.data.length);
this.offset += size;
};
/**
* Read a varint (type 2).
* @returns {BN}
*/
BufferReader.prototype.readVarint2BN = function readVarint2BN() {
var result = encoding.readVarint2BN(this.data, this.offset);
this.offset += result.size;
return result.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) {
var ret;
assert(size >= 0);
assert(this.offset + size <= this.data.length);
if (this.zeroCopy || zeroCopy) {
ret = this.data.slice(this.offset, this.offset + size);
} else {
ret = new Buffer(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) {
var ret;
assert(size >= 0);
assert(this.offset + size <= this.data.length);
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) {
var size = this.readVarint();
assert(!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) {
var i, ret;
assert(this.offset + 1 <= this.data.length);
for (i = this.offset; i < this.data.length; i++) {
if (this.data[i] === 0)
break;
}
assert(i !== this.data.length);
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() {
var start = this.stack[this.stack.length - 1] || 0;
var data = this.data.slice(start, this.offset);
return crypto.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() {
var chk = this.createChecksum();
var checksum = this.readU32();
assert(chk === checksum, 'Checksum mismatch.');
return checksum;
};
/*
* Expose
*/
module.exports = BufferReader;