| "use strict"; |
| module.exports = Writer; |
| |
| var util = require("./util/minimal"); |
| |
| var BufferWriter; // cyclic |
| |
| var LongBits = util.LongBits, |
| base64 = util.base64, |
| utf8 = util.utf8; |
| |
| /** |
| * Constructs a new writer operation instance. |
| * @classdesc Scheduled writer operation. |
| * @constructor |
| * @param {function(*, Uint8Array, number)} fn Function to call |
| * @param {number} len Value byte length |
| * @param {*} val Value to write |
| * @ignore |
| */ |
| function Op(fn, len, val) { |
| |
| /** |
| * Function to call. |
| * @type {function(Uint8Array, number, *)} |
| */ |
| this.fn = fn; |
| |
| /** |
| * Value byte length. |
| * @type {number} |
| */ |
| this.len = len; |
| |
| /** |
| * Next operation. |
| * @type {Writer.Op|undefined} |
| */ |
| this.next = undefined; |
| |
| /** |
| * Value to write. |
| * @type {*} |
| */ |
| this.val = val; // type varies |
| } |
| |
| /* istanbul ignore next */ |
| function noop() {} // eslint-disable-line no-empty-function |
| |
| /** |
| * Constructs a new writer state instance. |
| * @classdesc Copied writer state. |
| * @memberof Writer |
| * @constructor |
| * @param {Writer} writer Writer to copy state from |
| * @ignore |
| */ |
| function State(writer) { |
| |
| /** |
| * Current head. |
| * @type {Writer.Op} |
| */ |
| this.head = writer.head; |
| |
| /** |
| * Current tail. |
| * @type {Writer.Op} |
| */ |
| this.tail = writer.tail; |
| |
| /** |
| * Current buffer length. |
| * @type {number} |
| */ |
| this.len = writer.len; |
| |
| /** |
| * Next state. |
| * @type {State|null} |
| */ |
| this.next = writer.states; |
| } |
| |
| /** |
| * Constructs a new writer instance. |
| * @classdesc Wire format writer using `Uint8Array` if available, otherwise `Array`. |
| * @constructor |
| */ |
| function Writer() { |
| |
| /** |
| * Current length. |
| * @type {number} |
| */ |
| this.len = 0; |
| |
| /** |
| * Operations head. |
| * @type {Object} |
| */ |
| this.head = new Op(noop, 0, 0); |
| |
| /** |
| * Operations tail |
| * @type {Object} |
| */ |
| this.tail = this.head; |
| |
| /** |
| * Linked forked states. |
| * @type {Object|null} |
| */ |
| this.states = null; |
| |
| // When a value is written, the writer calculates its byte length and puts it into a linked |
| // list of operations to perform when finish() is called. This both allows us to allocate |
| // buffers of the exact required size and reduces the amount of work we have to do compared |
| // to first calculating over objects and then encoding over objects. In our case, the encoding |
| // part is just a linked list walk calling operations with already prepared values. |
| } |
| |
| var create = function create() { |
| return util.Buffer |
| ? function create_buffer_setup() { |
| return (Writer.create = function create_buffer() { |
| return new BufferWriter(); |
| })(); |
| } |
| /* istanbul ignore next */ |
| : function create_array() { |
| return new Writer(); |
| }; |
| }; |
| |
| /** |
| * Creates a new writer. |
| * @function |
| * @returns {BufferWriter|Writer} A {@link BufferWriter} when Buffers are supported, otherwise a {@link Writer} |
| */ |
| Writer.create = create(); |
| |
| /** |
| * Allocates a buffer of the specified size. |
| * @param {number} size Buffer size |
| * @returns {Uint8Array} Buffer |
| */ |
| Writer.alloc = function alloc(size) { |
| return new util.Array(size); |
| }; |
| |
| // Use Uint8Array buffer pool in the browser, just like node does with buffers |
| /* istanbul ignore else */ |
| if (util.Array !== Array) |
| Writer.alloc = util.pool(Writer.alloc, util.Array.prototype.subarray); |
| |
| /** |
| * Pushes a new operation to the queue. |
| * @param {function(Uint8Array, number, *)} fn Function to call |
| * @param {number} len Value byte length |
| * @param {number} val Value to write |
| * @returns {Writer} `this` |
| * @private |
| */ |
| Writer.prototype._push = function push(fn, len, val) { |
| this.tail = this.tail.next = new Op(fn, len, val); |
| this.len += len; |
| return this; |
| }; |
| |
| function writeByte(val, buf, pos) { |
| buf[pos] = val & 255; |
| } |
| |
| function writeVarint32(val, buf, pos) { |
| while (val > 127) { |
| buf[pos++] = val & 127 | 128; |
| val >>>= 7; |
| } |
| buf[pos] = val; |
| } |
| |
| /** |
| * Constructs a new varint writer operation instance. |
| * @classdesc Scheduled varint writer operation. |
| * @extends Op |
| * @constructor |
| * @param {number} len Value byte length |
| * @param {number} val Value to write |
| * @ignore |
| */ |
| function VarintOp(len, val) { |
| this.len = len; |
| this.next = undefined; |
| this.val = val; |
| } |
| |
| VarintOp.prototype = Object.create(Op.prototype); |
| VarintOp.prototype.fn = writeVarint32; |
| |
| /** |
| * Writes an unsigned 32 bit value as a varint. |
| * @param {number} value Value to write |
| * @returns {Writer} `this` |
| */ |
| Writer.prototype.uint32 = function write_uint32(value) { |
| // here, the call to this.push has been inlined and a varint specific Op subclass is used. |
| // uint32 is by far the most frequently used operation and benefits significantly from this. |
| this.len += (this.tail = this.tail.next = new VarintOp( |
| (value = value >>> 0) |
| < 128 ? 1 |
| : value < 16384 ? 2 |
| : value < 2097152 ? 3 |
| : value < 268435456 ? 4 |
| : 5, |
| value)).len; |
| return this; |
| }; |
| |
| /** |
| * Writes a signed 32 bit value as a varint. |
| * @function |
| * @param {number} value Value to write |
| * @returns {Writer} `this` |
| */ |
| Writer.prototype.int32 = function write_int32(value) { |
| return value < 0 |
| ? this._push(writeVarint64, 10, LongBits.fromNumber(value)) // 10 bytes per spec |
| : this.uint32(value); |
| }; |
| |
| /** |
| * Writes a 32 bit value as a varint, zig-zag encoded. |
| * @param {number} value Value to write |
| * @returns {Writer} `this` |
| */ |
| Writer.prototype.sint32 = function write_sint32(value) { |
| return this.uint32((value << 1 ^ value >> 31) >>> 0); |
| }; |
| |
| function writeVarint64(val, buf, pos) { |
| while (val.hi) { |
| buf[pos++] = val.lo & 127 | 128; |
| val.lo = (val.lo >>> 7 | val.hi << 25) >>> 0; |
| val.hi >>>= 7; |
| } |
| while (val.lo > 127) { |
| buf[pos++] = val.lo & 127 | 128; |
| val.lo = val.lo >>> 7; |
| } |
| buf[pos++] = val.lo; |
| } |
| |
| /** |
| * Writes an unsigned 64 bit value as a varint. |
| * @param {Long|number|string} value Value to write |
| * @returns {Writer} `this` |
| * @throws {TypeError} If `value` is a string and no long library is present. |
| */ |
| Writer.prototype.uint64 = function write_uint64(value) { |
| var bits = LongBits.from(value); |
| return this._push(writeVarint64, bits.length(), bits); |
| }; |
| |
| /** |
| * Writes a signed 64 bit value as a varint. |
| * @function |
| * @param {Long|number|string} value Value to write |
| * @returns {Writer} `this` |
| * @throws {TypeError} If `value` is a string and no long library is present. |
| */ |
| Writer.prototype.int64 = Writer.prototype.uint64; |
| |
| /** |
| * Writes a signed 64 bit value as a varint, zig-zag encoded. |
| * @param {Long|number|string} value Value to write |
| * @returns {Writer} `this` |
| * @throws {TypeError} If `value` is a string and no long library is present. |
| */ |
| Writer.prototype.sint64 = function write_sint64(value) { |
| var bits = LongBits.from(value).zzEncode(); |
| return this._push(writeVarint64, bits.length(), bits); |
| }; |
| |
| /** |
| * Writes a boolish value as a varint. |
| * @param {boolean} value Value to write |
| * @returns {Writer} `this` |
| */ |
| Writer.prototype.bool = function write_bool(value) { |
| return this._push(writeByte, 1, value ? 1 : 0); |
| }; |
| |
| function writeFixed32(val, buf, pos) { |
| buf[pos ] = val & 255; |
| buf[pos + 1] = val >>> 8 & 255; |
| buf[pos + 2] = val >>> 16 & 255; |
| buf[pos + 3] = val >>> 24; |
| } |
| |
| /** |
| * Writes an unsigned 32 bit value as fixed 32 bits. |
| * @param {number} value Value to write |
| * @returns {Writer} `this` |
| */ |
| Writer.prototype.fixed32 = function write_fixed32(value) { |
| return this._push(writeFixed32, 4, value >>> 0); |
| }; |
| |
| /** |
| * Writes a signed 32 bit value as fixed 32 bits. |
| * @function |
| * @param {number} value Value to write |
| * @returns {Writer} `this` |
| */ |
| Writer.prototype.sfixed32 = Writer.prototype.fixed32; |
| |
| /** |
| * Writes an unsigned 64 bit value as fixed 64 bits. |
| * @param {Long|number|string} value Value to write |
| * @returns {Writer} `this` |
| * @throws {TypeError} If `value` is a string and no long library is present. |
| */ |
| Writer.prototype.fixed64 = function write_fixed64(value) { |
| var bits = LongBits.from(value); |
| return this._push(writeFixed32, 4, bits.lo)._push(writeFixed32, 4, bits.hi); |
| }; |
| |
| /** |
| * Writes a signed 64 bit value as fixed 64 bits. |
| * @function |
| * @param {Long|number|string} value Value to write |
| * @returns {Writer} `this` |
| * @throws {TypeError} If `value` is a string and no long library is present. |
| */ |
| Writer.prototype.sfixed64 = Writer.prototype.fixed64; |
| |
| /** |
| * Writes a float (32 bit). |
| * @function |
| * @param {number} value Value to write |
| * @returns {Writer} `this` |
| */ |
| Writer.prototype.float = function write_float(value) { |
| return this._push(util.float.writeFloatLE, 4, value); |
| }; |
| |
| /** |
| * Writes a double (64 bit float). |
| * @function |
| * @param {number} value Value to write |
| * @returns {Writer} `this` |
| */ |
| Writer.prototype.double = function write_double(value) { |
| return this._push(util.float.writeDoubleLE, 8, value); |
| }; |
| |
| var writeBytes = util.Array.prototype.set |
| ? function writeBytes_set(val, buf, pos) { |
| buf.set(val, pos); // also works for plain array values |
| } |
| /* istanbul ignore next */ |
| : function writeBytes_for(val, buf, pos) { |
| for (var i = 0; i < val.length; ++i) |
| buf[pos + i] = val[i]; |
| }; |
| |
| /** |
| * Writes a sequence of bytes. |
| * @param {Uint8Array|string} value Buffer or base64 encoded string to write |
| * @returns {Writer} `this` |
| */ |
| Writer.prototype.bytes = function write_bytes(value) { |
| var len = value.length >>> 0; |
| if (!len) |
| return this._push(writeByte, 1, 0); |
| if (util.isString(value)) { |
| var buf = Writer.alloc(len = base64.length(value)); |
| base64.decode(value, buf, 0); |
| value = buf; |
| } |
| return this.uint32(len)._push(writeBytes, len, value); |
| }; |
| |
| /** |
| * Writes a string. |
| * @param {string} value Value to write |
| * @returns {Writer} `this` |
| */ |
| Writer.prototype.string = function write_string(value) { |
| var len = utf8.length(value); |
| return len |
| ? this.uint32(len)._push(utf8.write, len, value) |
| : this._push(writeByte, 1, 0); |
| }; |
| |
| /** |
| * Forks this writer's state by pushing it to a stack. |
| * Calling {@link Writer#reset|reset} or {@link Writer#ldelim|ldelim} resets the writer to the previous state. |
| * @returns {Writer} `this` |
| */ |
| Writer.prototype.fork = function fork() { |
| this.states = new State(this); |
| this.head = this.tail = new Op(noop, 0, 0); |
| this.len = 0; |
| return this; |
| }; |
| |
| /** |
| * Resets this instance to the last state. |
| * @returns {Writer} `this` |
| */ |
| Writer.prototype.reset = function reset() { |
| if (this.states) { |
| this.head = this.states.head; |
| this.tail = this.states.tail; |
| this.len = this.states.len; |
| this.states = this.states.next; |
| } else { |
| this.head = this.tail = new Op(noop, 0, 0); |
| this.len = 0; |
| } |
| return this; |
| }; |
| |
| /** |
| * Resets to the last state and appends the fork state's current write length as a varint followed by its operations. |
| * @returns {Writer} `this` |
| */ |
| Writer.prototype.ldelim = function ldelim() { |
| var head = this.head, |
| tail = this.tail, |
| len = this.len; |
| this.reset().uint32(len); |
| if (len) { |
| this.tail.next = head.next; // skip noop |
| this.tail = tail; |
| this.len += len; |
| } |
| return this; |
| }; |
| |
| /** |
| * Finishes the write operation. |
| * @returns {Uint8Array} Finished buffer |
| */ |
| Writer.prototype.finish = function finish() { |
| var head = this.head.next, // skip noop |
| buf = this.constructor.alloc(this.len), |
| pos = 0; |
| while (head) { |
| head.fn(head.val, buf, pos); |
| pos += head.len; |
| head = head.next; |
| } |
| // this.head = this.tail = null; |
| return buf; |
| }; |
| |
| Writer._configure = function(BufferWriter_) { |
| BufferWriter = BufferWriter_; |
| Writer.create = create(); |
| BufferWriter._configure(); |
| }; |