protobufjs-no-cli
Version:
Protocol Buffers for JavaScript. Finally.
284 lines (262 loc) • 10.7 kB
JavaScript
/**
* Constructs a new Message.
* @exports ProtoBuf.Reflect.Message
* @param {!ProtoBuf.Builder} builder Builder reference
* @param {!ProtoBuf.Reflect.Namespace} parent Parent message or namespace
* @param {string} name Message name
* @param {Object.<string,*>=} options Message options
* @param {boolean=} isGroup `true` if this is a legacy group
* @param {string?} syntax The syntax level of this definition (e.g., proto3)
* @constructor
* @extends ProtoBuf.Reflect.Namespace
*/
var Message = function(builder, parent, name, options, isGroup, syntax) {
Namespace.call(this, builder, parent, name, options, syntax);
/**
* @override
*/
this.className = "Message";
/**
* Extensions range.
* @type {!Array.<number>|undefined}
* @expose
*/
this.extensions = undefined;
/**
* Runtime message class.
* @type {?function(new:ProtoBuf.Builder.Message)}
* @expose
*/
this.clazz = null;
/**
* Whether this is a legacy group or not.
* @type {boolean}
* @expose
*/
this.isGroup = !!isGroup;
// The following cached collections are used to efficiently iterate over or look up fields when decoding.
/**
* Cached fields.
* @type {?Array.<!ProtoBuf.Reflect.Message.Field>}
* @private
*/
this._fields = null;
/**
* Cached fields by id.
* @type {?Object.<number,!ProtoBuf.Reflect.Message.Field>}
* @private
*/
this._fieldsById = null;
/**
* Cached fields by name.
* @type {?Object.<string,!ProtoBuf.Reflect.Message.Field>}
* @private
*/
this._fieldsByName = null;
};
/**
* @alias ProtoBuf.Reflect.Message.prototype
* @inner
*/
var MessagePrototype = Message.prototype = Object.create(Namespace.prototype);
/**
* Builds the message and returns the runtime counterpart, which is a fully functional class.
* @see ProtoBuf.Builder.Message
* @param {boolean=} rebuild Whether to rebuild or not, defaults to false
* @return {ProtoBuf.Reflect.Message} Message class
* @throws {Error} If the message cannot be built
* @expose
*/
MessagePrototype.build = function(rebuild) {
if (this.clazz && !rebuild)
return this.clazz;
// Create the runtime Message class in its own scope
var clazz = (function(ProtoBuf, T) {
//? include("../Builder/Message.js");
return Message;
})(ProtoBuf, this);
// Static enums and prototyped sub-messages / cached collections
this._fields = [];
this._fieldsById = {};
this._fieldsByName = {};
for (var i=0, k=this.children.length, child; i<k; i++) {
child = this.children[i];
if (child instanceof Enum || child instanceof Message || child instanceof Service) {
if (clazz.hasOwnProperty(child.name))
throw Error("Illegal reflect child of "+this.toString(true)+": "+child.toString(true)+" cannot override static property '"+child.name+"'");
clazz[child.name] = child.build();
} else if (child instanceof Message.Field)
child.build(),
this._fields.push(child),
this._fieldsById[child.id] = child,
this._fieldsByName[child.name] = child;
else if (!(child instanceof Message.OneOf) && !(child instanceof Extension)) // Not built
throw Error("Illegal reflect child of "+this.toString(true)+": "+this.children[i].toString(true));
}
return this.clazz = clazz;
};
/**
* Encodes a runtime message's contents to the specified buffer.
* @param {!ProtoBuf.Builder.Message} message Runtime message to encode
* @param {ByteBuffer} buffer ByteBuffer to write to
* @param {boolean=} noVerify Whether to not verify field values, defaults to `false`
* @return {ByteBuffer} The ByteBuffer for chaining
* @throws {Error} If required fields are missing or the message cannot be encoded for another reason
* @expose
*/
MessagePrototype.encode = function(message, buffer, noVerify) {
var fieldMissing = null,
field;
for (var i=0, k=this._fields.length, val; i<k; ++i) {
field = this._fields[i];
val = message[field.name];
if (field.required && val === null) {
if (fieldMissing === null)
fieldMissing = field;
} else
field.encode(noVerify ? val : field.verifyValue(val), buffer, message);
}
if (fieldMissing !== null) {
var err = Error("Missing at least one required field for "+this.toString(true)+": "+fieldMissing);
err["encoded"] = buffer; // Still expose what we got
throw(err);
}
return buffer;
};
/**
* Calculates a runtime message's byte length.
* @param {!ProtoBuf.Builder.Message} message Runtime message to encode
* @returns {number} Byte length
* @throws {Error} If required fields are missing or the message cannot be calculated for another reason
* @expose
*/
MessagePrototype.calculate = function(message) {
for (var n=0, i=0, k=this._fields.length, field, val; i<k; ++i) {
field = this._fields[i];
val = message[field.name];
if (field.required && val === null)
throw Error("Missing at least one required field for "+this.toString(true)+": "+field);
else
n += field.calculate(val, message);
}
return n;
};
/**
* Skips all data until the end of the specified group has been reached.
* @param {number} expectedId Expected GROUPEND id
* @param {!ByteBuffer} buf ByteBuffer
* @returns {boolean} `true` if a value as been skipped, `false` if the end has been reached
* @throws {Error} If it wasn't possible to find the end of the group (buffer overrun or end tag mismatch)
* @inner
*/
function skipTillGroupEnd(expectedId, buf) {
var tag = buf.readVarint32(), // Throws on OOB
wireType = tag & 0x07,
id = tag >>> 3;
switch (wireType) {
case ProtoBuf.WIRE_TYPES.VARINT:
do tag = buf.readUint8();
while ((tag & 0x80) === 0x80);
break;
case ProtoBuf.WIRE_TYPES.BITS64:
buf.offset += 8;
break;
case ProtoBuf.WIRE_TYPES.LDELIM:
tag = buf.readVarint32(); // reads the varint
buf.offset += tag; // skips n bytes
break;
case ProtoBuf.WIRE_TYPES.STARTGROUP:
skipTillGroupEnd(id, buf);
break;
case ProtoBuf.WIRE_TYPES.ENDGROUP:
if (id === expectedId)
return false;
else
throw Error("Illegal GROUPEND after unknown group: "+id+" ("+expectedId+" expected)");
case ProtoBuf.WIRE_TYPES.BITS32:
buf.offset += 4;
break;
default:
throw Error("Illegal wire type in unknown group "+expectedId+": "+wireType);
}
return true;
}
/**
* Decodes an encoded message and returns the decoded message.
* @param {ByteBuffer} buffer ByteBuffer to decode from
* @param {number=} length Message length. Defaults to decode all remaining data.
* @param {number=} expectedGroupEndId Expected GROUPEND id if this is a legacy group
* @return {ProtoBuf.Builder.Message} Decoded message
* @throws {Error} If the message cannot be decoded
* @expose
*/
MessagePrototype.decode = function(buffer, length, expectedGroupEndId) {
if (typeof length !== 'number')
length = -1;
var start = buffer.offset,
msg = new (this.clazz)(),
tag, wireType, id, field;
while (buffer.offset < start+length || (length === -1 && buffer.remaining() > 0)) {
tag = buffer.readVarint32();
wireType = tag & 0x07;
id = tag >>> 3;
if (wireType === ProtoBuf.WIRE_TYPES.ENDGROUP) {
if (id !== expectedGroupEndId)
throw Error("Illegal group end indicator for "+this.toString(true)+": "+id+" ("+(expectedGroupEndId ? expectedGroupEndId+" expected" : "not a group")+")");
break;
}
if (!(field = this._fieldsById[id])) {
// "messages created by your new code can be parsed by your old code: old binaries simply ignore the new field when parsing."
switch (wireType) {
case ProtoBuf.WIRE_TYPES.VARINT:
buffer.readVarint32();
break;
case ProtoBuf.WIRE_TYPES.BITS32:
buffer.offset += 4;
break;
case ProtoBuf.WIRE_TYPES.BITS64:
buffer.offset += 8;
break;
case ProtoBuf.WIRE_TYPES.LDELIM:
var len = buffer.readVarint32();
buffer.offset += len;
break;
case ProtoBuf.WIRE_TYPES.STARTGROUP:
while (skipTillGroupEnd(id, buffer)) {}
break;
default:
throw Error("Illegal wire type for unknown field "+id+" in "+this.toString(true)+"#decode: "+wireType);
}
continue;
}
if (field.repeated && !field.options["packed"]) {
msg[field.name].push(field.decode(wireType, buffer));
} else if (field.map) {
var keyval = field.decode(wireType, buffer);
msg[field.name].set(keyval[0], keyval[1]);
} else {
msg[field.name] = field.decode(wireType, buffer);
if (field.oneof) { // Field is part of an OneOf (not a virtual OneOf field)
var currentField = msg[field.oneof.name]; // Virtual field references currently set field
if (currentField !== null && currentField !== field.name)
msg[currentField] = null; // Clear currently set field
msg[field.oneof.name] = field.name; // Point virtual field at this field
}
}
}
// Check if all required fields are present and set default values for optional fields that are not
for (var i=0, k=this._fields.length; i<k; ++i) {
field = this._fields[i];
if (msg[field.name] === null) {
if (this.syntax === "proto3") { // Proto3 sets default values by specification
msg[field.name] = field.defaultValue;
} else if (field.required) {
var err = Error("Missing at least one required field for " + this.toString(true) + ": " + field.name);
err["decoded"] = msg; // Still expose what we got
throw(err);
} else if (ProtoBuf.populateDefaults && field.defaultValue !== null)
msg[field.name] = field.defaultValue;
}
}
return msg;
};