protobufjs
Version:
Protocol Buffers for JavaScript & TypeScript.
789 lines (704 loc) • 22.7 kB
JavaScript
"use strict";
module.exports = Reader;
var util = require("./util/minimal");
var BufferReader; // cyclic
var LongBits = util.LongBits,
utf8 = util.utf8;
/* istanbul ignore next */
function indexOutOfRange(reader, writeLength) {
return RangeError("index out of range: " + reader.pos + " + " + (writeLength || 1) + " > " + reader.len);
}
/**
* Constructs a new reader instance using the specified buffer.
* @classdesc Wire format reader using `Uint8Array`.
* @constructor
* @param {Uint8Array} buffer Buffer to read from
*/
function Reader(buffer) {
/**
* Read buffer.
* @type {Uint8Array}
*/
this.buf = buffer;
/**
* Read buffer position.
* @type {number}
*/
this.pos = 0;
/**
* Read buffer length.
* @type {number}
*/
this.len = buffer.length;
/**
* Cached DataView for packed reads.
* @type {DataView|null}
*/
this.view = null;
/**
* Whether to discard unknown fields while decoding.
* @type {boolean}
*/
this.discardUnknown = Reader.discardUnknown;
}
function create_array(buffer) {
// TODO: Remove plain array reader support in the next major release.
if (Array.isArray(buffer))
buffer = new Uint8Array(buffer);
if (buffer instanceof Uint8Array)
return new Reader(buffer);
throw Error("illegal buffer");
}
var create = function create() {
return util.Buffer
? function create_buffer_setup(buffer) {
return (Reader.create = function create_buffer(buffer) {
return util.Buffer.isBuffer(buffer)
? new BufferReader(buffer)
/* istanbul ignore next */
: create_array(buffer);
})(buffer);
}
/* istanbul ignore next */
: create_array;
};
/**
* Creates a new reader using the specified buffer.
* @function
* @param {Uint8Array|Buffer} buffer Buffer to read from
* @returns {Reader|BufferReader} A {@link BufferReader} if `buffer` is a Buffer, otherwise a {@link Reader}
* @throws {Error} If `buffer` is not a valid buffer
*/
Reader.create = create();
/**
* Returns raw bytes from the backing buffer without advancing the reader.
* @param {number} start Start offset
* @param {number} end End offset
* @returns {Uint8Array} Raw bytes
*/
Reader.prototype.raw = function read_raw(start, end) {
return this.buf.subarray(start, end);
};
/**
* Reads a varint as an unsigned 32 bit value.
* @function
* @returns {number} Value read
*/
Reader.prototype.uint32 = function read_uint32() {
var buf = this.buf,
pos = this.pos,
value = (buf[pos] & 127) >>> 0;
if (buf[pos++] < 128) {
this.pos = pos;
return value;
}
value = (value | (buf[pos] & 127) << 7) >>> 0;
if (buf[pos++] < 128) {
this.pos = pos;
return value;
}
value = (value | (buf[pos] & 127) << 14) >>> 0;
if (buf[pos++] < 128) {
this.pos = pos;
return value;
}
value = (value | (buf[pos] & 127) << 21) >>> 0;
if (buf[pos++] < 128) {
this.pos = pos;
return value;
}
value = (value | (buf[pos] & 15) << 28) >>> 0;
if (buf[pos++] < 128) {
this.pos = pos;
return value;
}
for (var i = 0; i < 5; ++i) {
/* istanbul ignore if */
if (pos >= this.len) {
this.pos = pos;
throw indexOutOfRange(this);
}
if (buf[pos++] < 128) {
this.pos = pos;
return value;
}
}
/* istanbul ignore next */
this.pos = pos;
throw Error("invalid varint encoding");
};
/**
* Reads a field tag.
* @function
* @returns {number} Tag read
*/
Reader.prototype.tag = function read_tag() {
var buf = this.buf,
pos = this.pos,
value = (buf[pos] & 127) >>> 0;
if (buf[pos++] < 128) {
this.pos = pos;
return value;
}
value = (value | (buf[pos] & 127) << 7) >>> 0;
if (buf[pos++] < 128) {
this.pos = pos;
return value;
}
value = (value | (buf[pos] & 127) << 14) >>> 0;
if (buf[pos++] < 128) {
this.pos = pos;
return value;
}
value = (value | (buf[pos] & 127) << 21) >>> 0;
if (buf[pos++] < 128) {
this.pos = pos;
return value;
}
value = (value | (buf[pos] & 15) << 28) >>> 0;
if (buf[pos] < 128 && (buf[pos] & 112) === 0) {
this.pos = pos + 1;
return value;
}
this.pos = pos + 1;
throw Error("invalid tag encoding");
};
/**
* Reads a varint as a signed 32 bit value.
* @returns {number} Value read
*/
Reader.prototype.int32 = function read_int32() {
return this.uint32() | 0;
};
/**
* Reads a zig-zag encoded varint as a signed 32 bit value.
* @returns {number} Value read
*/
Reader.prototype.sint32 = function read_sint32() {
var value = this.uint32();
return value >>> 1 ^ -(value & 1) | 0;
};
/* eslint-disable no-invalid-this */
function readLongVarint() {
// tends to deopt with local vars for octet etc.
var bits = new LongBits(0, 0);
var i = 0;
if (this.len - this.pos > 4) { // fast route (lo)
for (; i < 4; ++i) {
// 1st..4th
bits.lo = (bits.lo | (this.buf[this.pos] & 127) << i * 7) >>> 0;
if (this.buf[this.pos++] < 128)
return bits;
}
// 5th
bits.lo = (bits.lo | (this.buf[this.pos] & 127) << 28) >>> 0;
bits.hi = (bits.hi | (this.buf[this.pos] & 127) >> 4) >>> 0;
if (this.buf[this.pos++] < 128)
return bits;
i = 0;
} else {
for (; i < 4; ++i) {
/* istanbul ignore if */
if (this.pos >= this.len)
throw indexOutOfRange(this);
// 1st..4th
bits.lo = (bits.lo | (this.buf[this.pos] & 127) << i * 7) >>> 0;
if (this.buf[this.pos++] < 128)
return bits;
}
throw indexOutOfRange(this);
}
if (this.len - this.pos > 4) { // fast route (hi)
for (; i < 5; ++i) {
// 6th..10th
bits.hi = (bits.hi | (this.buf[this.pos] & 127) << i * 7 + 3) >>> 0;
if (this.buf[this.pos++] < 128)
return bits;
}
} else {
for (; i < 5; ++i) {
/* istanbul ignore if */
if (this.pos >= this.len)
throw indexOutOfRange(this);
// 6th..10th
bits.hi = (bits.hi | (this.buf[this.pos] & 127) << i * 7 + 3) >>> 0;
if (this.buf[this.pos++] < 128)
return bits;
}
}
/* istanbul ignore next */
throw Error("invalid varint encoding");
}
/* eslint-enable no-invalid-this */
/**
* Reads a varint as a signed 64 bit value.
* @name Reader#int64
* @function
* @returns {Long} Value read
*/
/**
* Reads a varint as an unsigned 64 bit value.
* @name Reader#uint64
* @function
* @returns {Long} Value read
*/
/**
* Reads a zig-zag encoded varint as a signed 64 bit value.
* @name Reader#sint64
* @function
* @returns {Long} Value read
*/
/**
* Reads a varint as a boolean.
* @returns {boolean} Value read
*/
Reader.prototype.bool = function read_bool() {
var value = false,
b;
for (var i = 0; i < 10; ++i) {
/* istanbul ignore if */
if (this.pos >= this.len)
throw indexOutOfRange(this);
b = this.buf[this.pos++];
if (b & 127)
value = true;
if (b < 128)
return value;
}
/* istanbul ignore next */
throw Error("invalid varint encoding");
};
function readFixed32_end(buf, end) { // note that this uses `end`, not `pos`
return (buf[end - 4]
| buf[end - 3] << 8
| buf[end - 2] << 16
| buf[end - 1] << 24) >>> 0;
}
/**
* Reads fixed 32 bits as an unsigned 32 bit integer.
* @returns {number} Value read
*/
Reader.prototype.fixed32 = function read_fixed32() {
/* istanbul ignore if */
if (this.pos + 4 > this.len)
throw indexOutOfRange(this, 4);
return readFixed32_end(this.buf, this.pos += 4);
};
/**
* Reads fixed 32 bits as a signed 32 bit integer.
* @returns {number} Value read
*/
Reader.prototype.sfixed32 = function read_sfixed32() {
/* istanbul ignore if */
if (this.pos + 4 > this.len)
throw indexOutOfRange(this, 4);
return readFixed32_end(this.buf, this.pos += 4) | 0;
};
/* eslint-disable no-invalid-this */
function readFixed64(/* this: Reader */) {
/* istanbul ignore if */
if (this.pos + 8 > this.len)
throw indexOutOfRange(this, 8);
return new LongBits(readFixed32_end(this.buf, this.pos += 4), readFixed32_end(this.buf, this.pos += 4));
}
/* eslint-enable no-invalid-this */
/**
* Reads fixed 64 bits.
* @name Reader#fixed64
* @function
* @returns {Long} Value read
*/
/**
* Reads zig-zag encoded fixed 64 bits.
* @name Reader#sfixed64
* @function
* @returns {Long} Value read
*/
/**
* Reads a float (32 bit) as a number.
* @function
* @returns {number} Value read
*/
Reader.prototype.float = function read_float() {
/* istanbul ignore if */
if (this.pos + 4 > this.len)
throw indexOutOfRange(this, 4);
var value = util.float.readFloatLE(this.buf, this.pos);
this.pos += 4;
return value;
};
/**
* Reads a double (64 bit float) as a number.
* @function
* @returns {number} Value read
*/
Reader.prototype.double = function read_double() {
/* istanbul ignore if */
if (this.pos + 8 > this.len)
throw indexOutOfRange(this, 4);
var value = util.float.readDoubleLE(this.buf, this.pos);
this.pos += 8;
return value;
};
/**
* Reads a packed repeated field of unsigned 32 bit varints.
* @param {number[]} [array] Array to read into; a new one is created if omitted
* @returns {number[]} Array read into
*/
Reader.prototype.uint32s = function read_uint32s(array) {
if (array === undefined) array = [];
var end = this.uint32() + this.pos;
while (this.pos < end)
array.push(this.uint32());
return array;
};
/**
* Reads a packed repeated field of signed 32 bit varints.
* @param {number[]} [array] Array to read into; a new one is created if omitted
* @returns {number[]} Array read into
*/
Reader.prototype.int32s = function read_int32s(array) {
if (array === undefined) array = [];
var end = this.uint32() + this.pos;
while (this.pos < end)
array.push(this.int32());
return array;
};
/**
* Reads a packed repeated field of zig-zag encoded signed 32 bit varints.
* @param {number[]} [array] Array to read into; a new one is created if omitted
* @returns {number[]} Array read into
*/
Reader.prototype.sint32s = function read_sint32s(array) {
if (array === undefined) array = [];
var end = this.uint32() + this.pos;
while (this.pos < end)
array.push(this.sint32());
return array;
};
/**
* Reads a packed repeated field of booleans.
* @param {boolean[]} [array] Array to read into; a new one is created if omitted
* @returns {boolean[]} Array read into
*/
Reader.prototype.bools = function read_bools(array) {
if (array === undefined) array = [];
var end = this.uint32() + this.pos;
while (this.pos < end)
array.push(this.bool());
return array;
};
// The view allocation only pays off when amortized over enough reads
var VIEW_THRESHOLD_FLOAT = 8,
VIEW_THRESHOLD_INT = 128;
function getLazyView(reader, count, threshold) {
var view = reader.view;
if (view || count < threshold)
return view;
var buf = reader.buf;
return reader.view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
}
/**
* Reads a packed repeated field of unsigned 32 bit fixed values.
* @param {number[]} [array] Array to read into; a new one is created if omitted
* @returns {number[]} Array read into
*/
Reader.prototype.fixed32s = function read_fixed32s(array) {
if (array === undefined) array = [];
var len = this.uint32(), end = this.pos + len;
/* istanbul ignore if */
if (end > this.len) throw indexOutOfRange(this, len);
var count = len >>> 2, i = array.length, pos = this.pos;
array.length = i + count;
var dv = getLazyView(this, count, VIEW_THRESHOLD_INT);
if (dv)
for (var k = 0; k < count; ++k, pos += 4) array[i++] = dv.getUint32(pos, true);
else {
var buf = this.buf;
for (var j = 0; j < count; ++j, pos += 4) array[i++] = readFixed32_end(buf, pos + 4);
}
this.pos = pos;
if (pos !== end) throw indexOutOfRange(this, 4);
return array;
};
/**
* Reads a packed repeated field of signed 32 bit fixed values.
* @param {number[]} [array] Array to read into; a new one is created if omitted
* @returns {number[]} Array read into
*/
Reader.prototype.sfixed32s = function read_sfixed32s(array) {
if (array === undefined) array = [];
var len = this.uint32(), end = this.pos + len;
/* istanbul ignore if */
if (end > this.len) throw indexOutOfRange(this, len);
var count = len >>> 2, i = array.length, pos = this.pos;
array.length = i + count;
var dv = getLazyView(this, count, VIEW_THRESHOLD_INT);
if (dv)
for (var k = 0; k < count; ++k, pos += 4) array[i++] = dv.getInt32(pos, true);
else {
var buf = this.buf;
for (var j = 0; j < count; ++j, pos += 4) array[i++] = readFixed32_end(buf, pos + 4) | 0;
}
this.pos = pos;
if (pos !== end) throw indexOutOfRange(this, 4);
return array;
};
/**
* Reads a packed repeated field of floats (32 bit).
* @param {number[]} [array] Array to read into; a new one is created if omitted
* @returns {number[]} Array read into
*/
Reader.prototype.floats = function read_floats(array) {
if (array === undefined) array = [];
var len = this.uint32(), end = this.pos + len;
/* istanbul ignore if */
if (end > this.len) throw indexOutOfRange(this, len);
var count = len >>> 2, i = array.length, pos = this.pos;
array.length = i + count;
var dv = getLazyView(this, count, VIEW_THRESHOLD_FLOAT);
if (dv)
for (var k = 0; k < count; ++k, pos += 4) array[i++] = dv.getFloat32(pos, true);
else {
var buf = this.buf;
for (var j = 0; j < count; ++j, pos += 4) array[i++] = util.float.readFloatLE(buf, pos);
}
this.pos = pos;
if (pos !== end) throw indexOutOfRange(this, 4);
return array;
};
/**
* Reads a packed repeated field of doubles (64 bit float).
* @param {number[]} [array] Array to read into; a new one is created if omitted
* @returns {number[]} Array read into
*/
Reader.prototype.doubles = function read_doubles(array) {
if (array === undefined) array = [];
var len = this.uint32(), end = this.pos + len;
/* istanbul ignore if */
if (end > this.len) throw indexOutOfRange(this, len);
var count = len >>> 3, i = array.length, pos = this.pos;
array.length = i + count;
var dv = getLazyView(this, count, VIEW_THRESHOLD_FLOAT);
if (dv)
for (var k = 0; k < count; ++k, pos += 8) array[i++] = dv.getFloat64(pos, true);
else {
var buf = this.buf;
for (var j = 0; j < count; ++j, pos += 8) array[i++] = util.float.readDoubleLE(buf, pos);
}
this.pos = pos;
if (pos !== end) throw indexOutOfRange(this, 8);
return array;
};
/**
* Reads a packed repeated field of unsigned 64 bit varints.
* @param {Array.<Long|number>} [array] Array to read into; a new one is created if omitted
* @returns {Array.<Long|number>} Array read into
*/
Reader.prototype.uint64s = function read_uint64s(array) {
if (array === undefined) array = [];
var end = this.uint32() + this.pos;
while (this.pos < end)
array.push(this.uint64());
return array;
};
/**
* Reads a packed repeated field of signed 64 bit varints.
* @param {Array.<Long|number>} [array] Array to read into; a new one is created if omitted
* @returns {Array.<Long|number>} Array read into
*/
Reader.prototype.int64s = function read_int64s(array) {
if (array === undefined) array = [];
var end = this.uint32() + this.pos;
while (this.pos < end)
array.push(this.int64());
return array;
};
/**
* Reads a packed repeated field of zig-zag encoded signed 64 bit varints.
* @param {Array.<Long|number>} [array] Array to read into; a new one is created if omitted
* @returns {Array.<Long|number>} Array read into
*/
Reader.prototype.sint64s = function read_sint64s(array) {
if (array === undefined) array = [];
var end = this.uint32() + this.pos;
while (this.pos < end)
array.push(this.sint64());
return array;
};
/**
* Reads a packed repeated field of unsigned 64 bit fixed values.
* @param {Array.<Long|number>} [array] Array to read into; a new one is created if omitted
* @returns {Array.<Long|number>} Array read into
*/
Reader.prototype.fixed64s = function read_fixed64s(array) {
if (array === undefined) array = [];
var len = this.uint32(), end = this.pos + len, i = array.length;
/* istanbul ignore if */
if (end > this.len) throw indexOutOfRange(this, len);
var count = len >>> 3;
array.length = i + count; // 8 bytes per value, count is known
for (var j = 0; j < count; ++j)
array[i++] = this.fixed64();
if (this.pos !== end) throw indexOutOfRange(this, 8);
return array;
};
/**
* Reads a packed repeated field of signed 64 bit fixed values.
* @param {Array.<Long|number>} [array] Array to read into; a new one is created if omitted
* @returns {Array.<Long|number>} Array read into
*/
Reader.prototype.sfixed64s = function read_sfixed64s(array) {
if (array === undefined) array = [];
var len = this.uint32(), end = this.pos + len, i = array.length;
/* istanbul ignore if */
if (end > this.len) throw indexOutOfRange(this, len);
var count = len >>> 3;
array.length = i + count; // 8 bytes per value, count is known
for (var j = 0; j < count; ++j)
array[i++] = this.sfixed64();
if (this.pos !== end) throw indexOutOfRange(this, 8);
return array;
};
/**
* Reads a sequence of bytes preceeded by its length as a varint.
* @returns {Uint8Array} Value read
*/
Reader.prototype.bytes = function read_bytes() {
var length = this.uint32(),
start = this.pos,
end = this.pos + length;
/* istanbul ignore if */
if (end > this.len)
throw indexOutOfRange(this, length);
this.pos = end;
return this.raw(start, end);
};
/**
* Reads a string preceeded by its byte length as a varint.
* @returns {string} Value read
*/
Reader.prototype.string = function read_string() {
var length = this.uint32(),
start = this.pos,
end = this.pos + length;
/* istanbul ignore if */
if (end > this.len)
throw indexOutOfRange(this, length);
this.pos = end;
return utf8.read(this.buf, start, end);
};
/**
* Reads a string preceeded by its byte length as a varint, rejecting invalid UTF8.
* @returns {string} Value read
*/
Reader.prototype.stringVerify = function read_string_verify() {
var length = this.uint32(),
start = this.pos,
end = this.pos + length;
/* istanbul ignore if */
if (end > this.len)
throw indexOutOfRange(this, length);
this.pos = end;
return utf8.readStrict(this.buf, start, end);
};
/**
* Skips the specified number of bytes if specified, otherwise skips a varint.
* @param {number} [length] Length if known, otherwise a varint is assumed
* @returns {Reader} `this`
*/
Reader.prototype.skip = function skip(length) {
if (typeof length === "number") {
/* istanbul ignore if */
if (this.pos + length > this.len)
throw indexOutOfRange(this, length);
this.pos += length;
} else {
do {
/* istanbul ignore if */
if (this.pos >= this.len)
throw indexOutOfRange(this);
} while (this.buf[this.pos++] & 128);
}
return this;
};
/**
* Recursion limit.
* @type {number}
*/
Reader.recursionLimit = util.recursionLimit;
/**
* Whether readers discard unknown fields while decoding.
* @type {boolean}
*/
Reader.discardUnknown = true;
/**
* Skips the next element of the specified wire type.
* @param {number} wireType Wire type received
* @param {number} [depth] Depth of recursion to control nested calls; 0 if omitted
* @param {number} [fieldNumber] Field number for validating group end tags
* @returns {Reader} `this`
*/
Reader.prototype.skipType = function(wireType, depth, fieldNumber) {
if (depth === undefined) depth = 0;
if (depth > Reader.recursionLimit)
throw Error("max depth exceeded");
if (fieldNumber === 0)
throw Error("illegal tag: field number 0");
switch (wireType) {
case 0:
this.skip();
break;
case 1:
this.skip(8);
break;
case 2:
this.skip(this.uint32());
break;
case 3:
while (true) {
var tag = this.tag();
var nestedField = tag >>> 3;
wireType = tag & 7;
if (!nestedField)
throw Error("illegal tag: field number 0");
if (wireType === 4) {
if (fieldNumber !== undefined && nestedField !== fieldNumber)
throw Error("invalid end group tag");
break;
}
this.skipType(wireType, depth + 1, nestedField);
}
break;
case 5:
this.skip(4);
break;
/* istanbul ignore next */
default:
throw Error("invalid wire type " + wireType + " at offset " + this.pos);
}
return this;
};
Reader._configure = function(BufferReader_) {
BufferReader = BufferReader_;
Reader.create = create();
BufferReader._configure();
var fn = util.Long ? "toLong" : /* istanbul ignore next */ "toNumber";
util.merge(Reader.prototype, {
int64: function read_int64() {
return readLongVarint.call(this)[fn](false);
},
uint64: function read_uint64() {
return readLongVarint.call(this)[fn](true);
},
sint64: function read_sint64() {
return readLongVarint.call(this).zzDecode()[fn](false);
},
fixed64: function read_fixed64() {
return readFixed64.call(this)[fn](true);
},
sfixed64: function read_sfixed64() {
return readFixed64.call(this)[fn](false);
}
});
};