/*! * writer.js - buffer writer for bcoin * Copyright (c) 2014-2015, Fedor Indutny (MIT License) * Copyright (c) 2014-2016, Christopher Jeffrey (MIT License). * https://github.com/bcoin-org/bcoin */ 'use strict'; var utils = require('../utils/utils'); var crypto = require('../crypto/crypto'); var assert = require('assert'); /* * Constants */ var SEEK = 0; var UI8 = 1; var UI16 = 2; var UI16BE = 3; var UI32 = 4; var UI32BE = 5; var UI64 = 6; var UI64BE = 7; var I8 = 8; var I16 = 9; var I16BE = 10; var I32 = 11; var I32BE = 12; var I64 = 13; var I64BE = 14; var FL = 15; var FLBE = 16; var DBL = 17; var DBLBE = 18; var VARINT = 19; var VARINT2 = 20; var BYTES = 21; var STR = 22; var CHECKSUM = 23; var FILL = 24; /** * An object that allows writing of buffers in a * sane manner. This buffer writer is extremely * optimized since it does not actually write * anything until `render` is called. It makes * one allocation: at the end, once it knows the * size of the buffer to be allocated. Because * of this, it can also act as a size calculator * which is useful for guaging block size * without actually serializing any data. * @exports BufferWriter * @constructor * @param {(BufferWriter|Object)?} options */ function BufferWriter(options) { if (options instanceof BufferWriter) return options; if (!(this instanceof BufferWriter)) return new BufferWriter(options); this.data = []; this.written = 0; } /** * Allocate and render the final buffer. * @param {Boolean?} keep - Do not destroy the writer. * @returns {Buffer} Rendered buffer. */ BufferWriter.prototype.render = function render(keep) { var data = new Buffer(this.written); var off = 0; var i, item; for (i = 0; i < this.data.length; i++) { item = this.data[i]; switch (item[0]) { case SEEK: off += item[1]; break; case UI8: off = data.writeUInt8(item[1], off, true); break; case UI16: off = data.writeUInt16LE(item[1], off, true); break; case UI16BE: off = data.writeUInt16BE(item[1], off, true); break; case UI32: off = data.writeUInt32LE(item[1], off, true); break; case UI32BE: off = data.writeUInt32BE(item[1], off, true); break; case UI64: off = utils.writeU64(data, item[1], off); break; case UI64BE: off = utils.writeU64BE(data, item[1], off); break; case I8: off = data.writeInt8(item[1], off, true); break; case I16: off = data.writeInt16LE(item[1], off, true); break; case I16BE: off = data.writeInt16BE(item[1], off, true); break; case I32: off = data.writeInt32LE(item[1], off, true); break; case I32BE: off = data.writeInt32BE(item[1], off, true); break; case I64: off = utils.write64(data, item[1], off); break; case I64BE: off = utils.write64BE(data, item[1], off); break; case FL: off = data.writeFloatLE(item[1], off, true); break; case FLBE: off = data.writeFloatBE(item[1], off, true); break; 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: off += crypto.checksum(data.slice(0, off)).copy(data, off); break; case FILL: data.fill(item[1], off, off + item[2]); off += item[2]; break; default: assert(false, 'Bad type.'); break; } } assert(off === data.length); if (!keep) this.destroy(); return data; }; /** * Get size of data written so far. * @returns {Number} */ BufferWriter.prototype.getSize = function getSize() { return this.written; }; /** * Seek to relative offset. * @param {Number} offset */ BufferWriter.prototype.seek = function seek(offset) { this.data.push([SEEK, offset]); }; /** * Destroy the buffer writer. Remove references to `data`. */ BufferWriter.prototype.destroy = function destroy() { this.data.length = 0; this.data = null; this.written = null; }; /** * Write uint8. * @param {Number} value */ BufferWriter.prototype.writeU8 = function writeU8(value) { this.written += 1; this.data.push([UI8, value]); }; /** * Write uint16le. * @param {Number} value */ BufferWriter.prototype.writeU16 = function writeU16(value) { this.written += 2; this.data.push([UI16, value]); }; /** * Write uint16be. * @param {Number} value */ BufferWriter.prototype.writeU16BE = function writeU16BE(value) { this.written += 2; this.data.push([UI16BE, value]); }; /** * Write uint32le. * @param {Number} value */ BufferWriter.prototype.writeU32 = function writeU32(value) { this.written += 4; this.data.push([UI32, value]); }; /** * Write uint32be. * @param {Number} value */ BufferWriter.prototype.writeU32BE = function writeU32BE(value) { this.written += 4; this.data.push([UI32BE, value]); }; /** * Write uint64le. * @param {BN|Number} value */ BufferWriter.prototype.writeU64 = function writeU64(value) { this.written += 8; this.data.push([UI64, value]); }; /** * Write uint64be. * @param {BN|Number} value */ BufferWriter.prototype.writeU64BE = function writeU64BE(value) { this.written += 8; this.data.push([UI64BE, value]); }; /** * Write int8. * @param {Number} value */ BufferWriter.prototype.write8 = function write8(value) { this.written += 1; this.data.push([I8, value]); }; /** * Write int16le. * @param {Number} value */ BufferWriter.prototype.write16 = function write16(value) { this.written += 2; this.data.push([I16, value]); }; /** * Write int16be. * @param {Number} value */ BufferWriter.prototype.write16BE = function write16BE(value) { this.written += 2; this.data.push([I16BE, value]); }; /** * Write int32le. * @param {Number} value */ BufferWriter.prototype.write32 = function write32(value) { this.written += 4; this.data.push([I32, value]); }; /** * Write int32be. * @param {Number} value */ BufferWriter.prototype.write32BE = function write32BE(value) { this.written += 4; this.data.push([I32BE, value]); }; /** * Write int64le. * @param {BN|Number} value */ BufferWriter.prototype.write64 = function write64(value) { this.written += 8; this.data.push([I64, value]); }; /** * Write int64be. * @param {BN|Number} value */ BufferWriter.prototype.write64BE = function write64BE(value) { this.written += 8; this.data.push([I64BE, value]); }; /** * Write float le. * @param {Number} value */ BufferWriter.prototype.writeFloat = function writeFloat(value) { this.written += 4; this.data.push([FL, value]); }; /** * Write float be. * @param {Number} value */ BufferWriter.prototype.writeFloatBE = function writeFloatBE(value) { this.written += 4; this.data.push([FLBE, value]); }; /** * Write double le. * @param {Number} value */ BufferWriter.prototype.writeDouble = function writeDouble(value) { this.written += 8; this.data.push([DBL, value]); }; /** * Write double be. * @param {Number} value */ BufferWriter.prototype.writeDoubleBE = function writeDoubleBE(value) { this.written += 8; this.data.push([DBLBE, value]); }; /** * Write a varint. * @param {BN|Number} value */ BufferWriter.prototype.writeVarint = function writeVarint(value) { if (typeof value === 'number') assert(value >= 0); else assert(!value.isNeg()); this.written += utils.sizeVarint(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 */ BufferWriter.prototype.writeBytes = function writeBytes(value) { this.written += value.length; this.data.push([BYTES, value]); }; /** * Write bytes with a varint length before them. * @param {Buffer} value */ BufferWriter.prototype.writeVarBytes = function writeVarBytes(value) { this.written += utils.sizeVarint(value.length); this.written += value.length; this.data.push([VARINT, value.length]); this.data.push([BYTES, value]); }; /** * Write string to buffer. * @param {String|Buffer} value * @param {String?} enc - Any buffer-supported encoding. */ BufferWriter.prototype.writeString = function writeString(value, enc) { if (typeof value !== 'string') return this.writeBytes(value); this.written += Buffer.byteLength(value, enc); if (value.length === 0) return; this.data.push([STR, value, enc]); }; /** * Write a hash/hex-string. * @param {Hash|Buffer} */ BufferWriter.prototype.writeHash = function writeHash(value) { this.writeString(value, 'hex'); }; /** * Write a string with a varint length before it. * @param {String|Buffer} * @param {String?} enc - Any buffer-supported encoding. */ BufferWriter.prototype.writeVarString = function writeVarString(value, enc) { var size; if (typeof value !== 'string') return this.writeVarBytes(value); size = Buffer.byteLength(value, enc); this.written += utils.sizeVarint(size); this.written += size; this.data.push([VARINT, size]); if (value.length === 0) return; this.data.push([STR, value, enc]); }; /** * Write a null-terminated string. * @param {String|Buffer} * @param {String?} enc - Any buffer-supported encoding. */ BufferWriter.prototype.writeNullString = function writeNullString(value, enc) { this.writeString(value, enc); this.writeU8(0); }; /** * Calculate and write a checksum for the data written so far. */ BufferWriter.prototype.writeChecksum = function writeChecksum() { this.written += 4; this.data.push([CHECKSUM]); }; /** * Fill N bytes with value. * @param {Number} value * @param {Number} size */ BufferWriter.prototype.fill = function fill(value, size) { assert(size >= 0); if (size === 0) return; this.written += size; this.data.push([FILL, value, size]); }; /* * Expose */ module.exports = BufferWriter;