/* Copyright 2013 Daniel Wirtz Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ /** * @license ProtoBuf.js (c) 2013 Daniel Wirtz * Released under the Apache License, Version 2.0 * see: https://github.com/dcodeIO/ProtoBuf.js for details */ (function(global) { "use strict"; function init(ByteBuffer) { /** * The ProtoBuf namespace. * @exports ProtoBuf * @namespace * @expose */ var ProtoBuf = {}; /** * ProtoBuf.js version. * @type {string} * @const * @expose */ ProtoBuf.VERSION = "3.8.0"; /** * Wire types. * @type {Object.} * @const * @expose */ ProtoBuf.WIRE_TYPES = {}; /** * Varint wire type. * @type {number} * @expose */ ProtoBuf.WIRE_TYPES.VARINT = 0; /** * Fixed 64 bits wire type. * @type {number} * @const * @expose */ ProtoBuf.WIRE_TYPES.BITS64 = 1; /** * Length delimited wire type. * @type {number} * @const * @expose */ ProtoBuf.WIRE_TYPES.LDELIM = 2; /** * Start group wire type. * @type {number} * @const * @expose */ ProtoBuf.WIRE_TYPES.STARTGROUP = 3; /** * End group wire type. * @type {number} * @const * @expose */ ProtoBuf.WIRE_TYPES.ENDGROUP = 4; /** * Fixed 32 bits wire type. * @type {number} * @const * @expose */ ProtoBuf.WIRE_TYPES.BITS32 = 5; /** * Packable wire types. * @type {!Array.} * @const * @expose */ ProtoBuf.PACKABLE_WIRE_TYPES = [ ProtoBuf.WIRE_TYPES.VARINT, ProtoBuf.WIRE_TYPES.BITS64, ProtoBuf.WIRE_TYPES.BITS32 ]; /** * Types. * @dict * @type {Object.} * @const * @expose */ ProtoBuf.TYPES = { // According to the protobuf spec. "int32": { name: "int32", wireType: ProtoBuf.WIRE_TYPES.VARINT }, "uint32": { name: "uint32", wireType: ProtoBuf.WIRE_TYPES.VARINT }, "sint32": { name: "sint32", wireType: ProtoBuf.WIRE_TYPES.VARINT }, "int64": { name: "int64", wireType: ProtoBuf.WIRE_TYPES.VARINT }, "uint64": { name: "uint64", wireType: ProtoBuf.WIRE_TYPES.VARINT }, "sint64": { name: "sint64", wireType: ProtoBuf.WIRE_TYPES.VARINT }, "bool": { name: "bool", wireType: ProtoBuf.WIRE_TYPES.VARINT }, "double": { name: "double", wireType: ProtoBuf.WIRE_TYPES.BITS64 }, "string": { name: "string", wireType: ProtoBuf.WIRE_TYPES.LDELIM }, "bytes": { name: "bytes", wireType: ProtoBuf.WIRE_TYPES.LDELIM }, "fixed32": { name: "fixed32", wireType: ProtoBuf.WIRE_TYPES.BITS32 }, "sfixed32": { name: "sfixed32", wireType: ProtoBuf.WIRE_TYPES.BITS32 }, "fixed64": { name: "fixed64", wireType: ProtoBuf.WIRE_TYPES.BITS64 }, "sfixed64": { name: "sfixed64", wireType: ProtoBuf.WIRE_TYPES.BITS64 }, "float": { name: "float", wireType: ProtoBuf.WIRE_TYPES.BITS32 }, "enum": { name: "enum", wireType: ProtoBuf.WIRE_TYPES.VARINT }, "message": { name: "message", wireType: ProtoBuf.WIRE_TYPES.LDELIM }, "group": { name: "group", wireType: ProtoBuf.WIRE_TYPES.STARTGROUP } }; /** * Minimum field id. * @type {number} * @const * @expose */ ProtoBuf.ID_MIN = 1; /** * Maximum field id. * @type {number} * @const * @expose */ ProtoBuf.ID_MAX = 0x1FFFFFFF; /** * @type {!function(new: ByteBuffer, ...[*])} * @expose */ ProtoBuf.ByteBuffer = ByteBuffer; /** * @type {?function(new: Long, ...[*])} * @expose */ ProtoBuf.Long = ByteBuffer.Long || null; /** * If set to `true`, field names will be converted from underscore notation to camel case. Defaults to `false`. * Must be set prior to parsing. * @type {boolean} * @expose */ ProtoBuf.convertFieldsToCamelCase = false; /** * By default, messages are populated with (setX, set_x) accessors for each field. This can be disabled by * setting this to `false` prior to building messages. * @type {boolean} * @expose */ ProtoBuf.populateAccessors = true; /** * @alias ProtoBuf.Util * @expose */ ProtoBuf.Util = (function() { "use strict"; // Object.create polyfill // ref: https://developer.mozilla.org/de/docs/JavaScript/Reference/Global_Objects/Object/create if (!Object.create) /** @expose */ Object.create = function (o) { if (arguments.length > 1) throw Error('Object.create polyfill only accepts the first parameter.'); function F() {} F.prototype = o; return new F(); }; /** * ProtoBuf utilities. * @exports ProtoBuf.Util * @namespace */ var Util = {}; /** * Flag if running in node (fs is available) or not. * @type {boolean} * @const * @expose */ Util.IS_NODE = false; try { // There is no reliable way to detect node.js as an environment, so our // best bet is to feature-detect what we actually need. Util.IS_NODE = typeof require === 'function' && typeof require("fs").readFileSync === 'function' && typeof require("path").resolve === 'function'; } catch (e) {} /** * Constructs a XMLHttpRequest object. * @return {XMLHttpRequest} * @throws {Error} If XMLHttpRequest is not supported * @expose */ Util.XHR = function() { // No dependencies please, ref: http://www.quirksmode.org/js/xmlhttp.html var XMLHttpFactories = [ function () {return new XMLHttpRequest()}, function () {return new ActiveXObject("Msxml2.XMLHTTP")}, function () {return new ActiveXObject("Msxml3.XMLHTTP")}, function () {return new ActiveXObject("Microsoft.XMLHTTP")} ]; /** @type {?XMLHttpRequest} */ var xhr = null; for (var i=0;i} * @expose */ ProtoBuf.Lang = { OPEN: "{", CLOSE: "}", OPTOPEN: "[", OPTCLOSE: "]", OPTEND: ",", EQUAL: "=", END: ";", STRINGOPEN: '"', STRINGCLOSE: '"', STRINGOPEN_SQ: "'", STRINGCLOSE_SQ: "'", COPTOPEN: '(', COPTCLOSE: ')', DELIM: /[\s\{\}=;\[\],'"\(\)]/g, // KEYWORD: /^(?:package|option|import|message|enum|extend|service|syntax|extensions|group)$/, RULE: /^(?:required|optional|repeated)$/, TYPE: /^(?:double|float|int32|uint32|sint32|int64|uint64|sint64|fixed32|sfixed32|fixed64|sfixed64|bool|string|bytes)$/, NAME: /^[a-zA-Z_][a-zA-Z_0-9]*$/, TYPEDEF: /^[a-zA-Z][a-zA-Z_0-9]*$/, TYPEREF: /^(?:\.?[a-zA-Z_][a-zA-Z_0-9]*)+$/, FQTYPEREF: /^(?:\.[a-zA-Z][a-zA-Z_0-9]*)+$/, NUMBER: /^-?(?:[1-9][0-9]*|0|0x[0-9a-fA-F]+|0[0-7]+|([0-9]*\.[0-9]+([Ee][+-]?[0-9]+)?))$/, NUMBER_DEC: /^(?:[1-9][0-9]*|0)$/, NUMBER_HEX: /^0x[0-9a-fA-F]+$/, NUMBER_OCT: /^0[0-7]+$/, NUMBER_FLT: /^[0-9]*\.[0-9]+([Ee][+-]?[0-9]+)?$/, ID: /^(?:[1-9][0-9]*|0|0x[0-9a-fA-F]+|0[0-7]+)$/, NEGID: /^\-?(?:[1-9][0-9]*|0|0x[0-9a-fA-F]+|0[0-7]+)$/, WHITESPACE: /\s/, STRING: /['"]([^'"\\]*(\\.[^"\\]*)*)['"]/g, BOOL: /^(?:true|false)$/i }; /** * @alias ProtoBuf.DotProto * @expose */ ProtoBuf.DotProto = (function(ProtoBuf, Lang) { "use strict"; /** * Utilities to parse .proto files. * @exports ProtoBuf.DotProto * @namespace */ var DotProto = {}; /** * Constructs a new Tokenizer. * @exports ProtoBuf.DotProto.Tokenizer * @class prototype tokenizer * @param {string} proto Proto to tokenize * @constructor */ var Tokenizer = function(proto) { /** * Source to parse. * @type {string} * @expose */ this.source = ""+proto; // In case it's a buffer /** * Current index. * @type {number} * @expose */ this.index = 0; /** * Current line. * @type {number} * @expose */ this.line = 1; /** * Stacked values. * @type {Array} * @expose */ this.stack = []; /** * Whether currently reading a string or not. * @type {boolean} * @expose */ this.readingString = false; /** * Whatever character ends the string. Either a single or double quote character. * @type {string} * @expose */ this.stringEndsWith = Lang.STRINGCLOSE; }; /** * @alias ProtoBuf.DotProto.Tokenizer.prototype * @inner */ var TokenizerPrototype = Tokenizer.prototype; /** * Reads a string beginning at the current index. * @return {string} The string * @throws {Error} If it's not a valid string * @private */ TokenizerPrototype._readString = function() { Lang.STRING.lastIndex = this.index-1; // Include the open quote var match; if ((match = Lang.STRING.exec(this.source)) !== null) { var s = match[1]; this.index = Lang.STRING.lastIndex; this.stack.push(this.stringEndsWith); return s; } throw Error("Unterminated string at line "+this.line+", index "+this.index); }; /** * Gets the next token and advances by one. * @return {?string} Token or `null` on EOF * @throws {Error} If it's not a valid proto file * @expose */ TokenizerPrototype.next = function() { if (this.stack.length > 0) return this.stack.shift(); if (this.index >= this.source.length) return null; // No more tokens if (this.readingString) { this.readingString = false; return this._readString(); } var repeat, last; do { repeat = false; // Strip white spaces while (Lang.WHITESPACE.test(last = this.source.charAt(this.index))) { this.index++; if (last === "\n") this.line++; if (this.index === this.source.length) return null; } // Strip comments if (this.source.charAt(this.index) === '/') { if (this.source.charAt(++this.index) === '/') { // Single line while (this.source.charAt(this.index) !== "\n") { this.index++; if (this.index == this.source.length) return null; } this.index++; this.line++; repeat = true; } else if (this.source.charAt(this.index) === '*') { /* Block */ last = ''; while (last+(last=this.source.charAt(this.index)) !== '*/') { this.index++; if (last === "\n") this.line++; if (this.index === this.source.length) return null; } this.index++; repeat = true; } else throw Error("Unterminated comment at line "+this.line+": /"+this.source.charAt(this.index)); } } while (repeat); if (this.index === this.source.length) return null; // Read the next token var end = this.index; Lang.DELIM.lastIndex = 0; var delim = Lang.DELIM.test(this.source.charAt(end)); if (!delim) { ++end; while(end < this.source.length && !Lang.DELIM.test(this.source.charAt(end))) end++; } else ++end; var token = this.source.substring(this.index, this.index = end); if (token === Lang.STRINGOPEN) this.readingString = true, this.stringEndsWith = Lang.STRINGCLOSE; else if (token === Lang.STRINGOPEN_SQ) this.readingString = true, this.stringEndsWith = Lang.STRINGCLOSE_SQ; return token; }; /** * Peeks for the next token. * @return {?string} Token or `null` on EOF * @throws {Error} If it's not a valid proto file * @expose */ TokenizerPrototype.peek = function() { if (this.stack.length === 0) { var token = this.next(); if (token === null) return null; this.stack.push(token); } return this.stack[0]; }; /** * Returns a string representation of this object. * @return {string} String representation as of "Tokenizer(index/length)" * @expose */ TokenizerPrototype.toString = function() { return "Tokenizer("+this.index+"/"+this.source.length+" at line "+this.line+")"; }; /** * @alias ProtoBuf.DotProto.Tokenizer * @expose */ DotProto.Tokenizer = Tokenizer; /** * Constructs a new Parser. * @exports ProtoBuf.DotProto.Parser * @class prototype parser * @param {string} proto Protocol source * @constructor */ var Parser = function(proto) { /** * Tokenizer. * @type {ProtoBuf.DotProto.Tokenizer} * @expose */ this.tn = new Tokenizer(proto); }; /** * @alias ProtoBuf.DotProto.Parser.prototype * @inner */ var ParserPrototype = Parser.prototype; /** * Runs the parser. * @return {{package: string|null, messages: Array., enums: Array., imports: Array., options: object}} * @throws {Error} If the source cannot be parsed * @expose */ ParserPrototype.parse = function() { var topLevel = { "name": "[ROOT]", // temporary "package": null, "messages": [], "enums": [], "imports": [], "options": {}, "services": [] }; var token, head = true; while(token = this.tn.next()) { switch (token) { case 'package': if (!head || topLevel["package"] !== null) throw Error("Unexpected package at line "+this.tn.line); topLevel["package"] = this._parsePackage(token); break; case 'import': if (!head) throw Error("Unexpected import at line "+this.tn.line); topLevel.imports.push(this._parseImport(token)); break; case 'message': this._parseMessage(topLevel, null, token); head = false; break; case 'enum': this._parseEnum(topLevel, token); head = false; break; case 'option': if (!head) throw Error("Unexpected option at line "+this.tn.line); this._parseOption(topLevel, token); break; case 'service': this._parseService(topLevel, token); break; case 'extend': this._parseExtend(topLevel, token); break; case 'syntax': this._parseIgnoredStatement(topLevel, token); break; default: throw Error("Unexpected token at line "+this.tn.line+": "+token); } } delete topLevel["name"]; return topLevel; }; /** * Parses a number value. * @param {string} val Number value to parse * @return {number} Number * @throws {Error} If the number value is invalid * @private */ ParserPrototype._parseNumber = function(val) { var sign = 1; if (val.charAt(0) == '-') sign = -1, val = val.substring(1); if (Lang.NUMBER_DEC.test(val)) return sign*parseInt(val, 10); else if (Lang.NUMBER_HEX.test(val)) return sign*parseInt(val.substring(2), 16); else if (Lang.NUMBER_OCT.test(val)) return sign*parseInt(val.substring(1), 8); else if (Lang.NUMBER_FLT.test(val)) return sign*parseFloat(val); throw Error("Illegal number at line "+this.tn.line+": "+(sign < 0 ? '-' : '')+val); }; /** * Parses a (possibly multiline) string. * @returns {string} * @private */ ParserPrototype._parseString = function() { var value = "", token; do { token = this.tn.next(); // Known to be = this.tn.stringEndsWith value += this.tn.next(); token = this.tn.next(); if (token !== this.tn.stringEndsWith) throw Error("Illegal end of string at line "+this.tn.line+": "+token); token = this.tn.peek(); } while (token === Lang.STRINGOPEN || token === Lang.STRINGOPEN_SQ); return value; }; /** * Parses an ID value. * @param {string} val ID value to parse * @param {boolean=} neg Whether the ID may be negative, defaults to `false` * @returns {number} ID * @throws {Error} If the ID value is invalid * @private */ ParserPrototype._parseId = function(val, neg) { var id = -1; var sign = 1; if (val.charAt(0) == '-') sign = -1, val = val.substring(1); if (Lang.NUMBER_DEC.test(val)) id = parseInt(val); else if (Lang.NUMBER_HEX.test(val)) id = parseInt(val.substring(2), 16); else if (Lang.NUMBER_OCT.test(val)) id = parseInt(val.substring(1), 8); else throw Error("Illegal id at line "+this.tn.line+": "+(sign < 0 ? '-' : '')+val); id = (sign*id)|0; // Force to 32bit if (!neg && id < 0) throw Error("Illegal id at line "+this.tn.line+": "+(sign < 0 ? '-' : '')+val); return id; }; /** * Parses the package definition. * @param {string} token Initial token * @return {string} Package name * @throws {Error} If the package definition cannot be parsed * @private */ ParserPrototype._parsePackage = function(token) { token = this.tn.next(); if (!Lang.TYPEREF.test(token)) throw Error("Illegal package name at line "+this.tn.line+": "+token); var pkg = token; token = this.tn.next(); if (token != Lang.END) throw Error("Illegal end of package at line "+this.tn.line+": "+token); return pkg; }; /** * Parses an import definition. * @param {string} token Initial token * @return {string} Import file name * @throws {Error} If the import definition cannot be parsed * @private */ ParserPrototype._parseImport = function(token) { token = this.tn.peek(); if (token === "public") this.tn.next(), token = this.tn.peek(); if (token !== Lang.STRINGOPEN && token !== Lang.STRINGOPEN_SQ) throw Error("Illegal start of import at line "+this.tn.line+": "+token); var imported = this._parseString(); token = this.tn.next(); if (token !== Lang.END) throw Error("Illegal end of import at line "+this.tn.line+": "+token); return imported; }; /** * Parses a namespace option. * @param {Object} parent Parent definition * @param {string} token Initial token * @throws {Error} If the option cannot be parsed * @private */ ParserPrototype._parseOption = function(parent, token) { token = this.tn.next(); var custom = false; if (token == Lang.COPTOPEN) custom = true, token = this.tn.next(); if (!Lang.TYPEREF.test(token)) // we can allow options of the form google.protobuf.* since they will just get ignored anyways if (!/google\.protobuf\./.test(token)) throw Error("Illegal option name in message "+parent.name+" at line "+this.tn.line+": "+token); var name = token; token = this.tn.next(); if (custom) { // (my_method_option).foo, (my_method_option), some_method_option, (foo.my_option).bar if (token !== Lang.COPTCLOSE) throw Error("Illegal end in message "+parent.name+", option "+name+" at line "+this.tn.line+": "+token); name = '('+name+')'; token = this.tn.next(); if (Lang.FQTYPEREF.test(token)) name += token, token = this.tn.next(); } if (token !== Lang.EQUAL) throw Error("Illegal operator in message "+parent.name+", option "+name+" at line "+this.tn.line+": "+token); var value; token = this.tn.peek(); if (token === Lang.STRINGOPEN || token === Lang.STRINGOPEN_SQ) value = this._parseString(); else { this.tn.next(); if (Lang.NUMBER.test(token)) value = this._parseNumber(token, true); else if (Lang.BOOL.test(token)) value = token === 'true'; else if (Lang.TYPEREF.test(token)) value = token; else throw Error("Illegal option value in message "+parent.name+", option "+name+" at line "+this.tn.line+": "+token); } token = this.tn.next(); if (token !== Lang.END) throw Error("Illegal end of option in message "+parent.name+", option "+name+" at line "+this.tn.line+": "+token); parent["options"][name] = value; }; /** * Parses an ignored statement of the form ['keyword', ..., ';']. * @param {Object} parent Parent definition * @param {string} keyword Initial token * @throws {Error} If the directive cannot be parsed * @private */ ParserPrototype._parseIgnoredStatement = function(parent, keyword) { var token; do { token = this.tn.next(); if (token === null) throw Error("Unexpected EOF in "+parent.name+", "+keyword+" at line "+this.tn.line); if (token === Lang.END) break; } while (true); }; /** * Parses a service definition. * @param {Object} parent Parent definition * @param {string} token Initial token * @throws {Error} If the service cannot be parsed * @private */ ParserPrototype._parseService = function(parent, token) { token = this.tn.next(); if (!Lang.NAME.test(token)) throw Error("Illegal service name at line "+this.tn.line+": "+token); var name = token; var svc = { "name": name, "rpc": {}, "options": {} }; token = this.tn.next(); if (token !== Lang.OPEN) throw Error("Illegal start of service "+name+" at line "+this.tn.line+": "+token); do { token = this.tn.next(); if (token === "option") this._parseOption(svc, token); else if (token === 'rpc') this._parseServiceRPC(svc, token); else if (token !== Lang.CLOSE) throw Error("Illegal type of service "+name+" at line "+this.tn.line+": "+token); } while (token !== Lang.CLOSE); parent["services"].push(svc); }; /** * Parses a RPC service definition of the form ['rpc', name, (request), 'returns', (response)]. * @param {Object} svc Parent definition * @param {string} token Initial token * @private */ ParserPrototype._parseServiceRPC = function(svc, token) { var type = token; token = this.tn.next(); if (!Lang.NAME.test(token)) throw Error("Illegal method name in service "+svc["name"]+" at line "+this.tn.line+": "+token); var name = token; var method = { "request": null, "response": null, "options": {} }; token = this.tn.next(); if (token !== Lang.COPTOPEN) throw Error("Illegal start of request type in service "+svc["name"]+"#"+name+" at line "+this.tn.line+": "+token); token = this.tn.next(); if (!Lang.TYPEREF.test(token)) throw Error("Illegal request type in service "+svc["name"]+"#"+name+" at line "+this.tn.line+": "+token); method["request"] = token; token = this.tn.next(); if (token != Lang.COPTCLOSE) throw Error("Illegal end of request type in service "+svc["name"]+"#"+name+" at line "+this.tn.line+": "+token); token = this.tn.next(); if (token.toLowerCase() !== "returns") throw Error("Illegal delimiter in service "+svc["name"]+"#"+name+" at line "+this.tn.line+": "+token); token = this.tn.next(); if (token != Lang.COPTOPEN) throw Error("Illegal start of response type in service "+svc["name"]+"#"+name+" at line "+this.tn.line+": "+token); token = this.tn.next(); method["response"] = token; token = this.tn.next(); if (token !== Lang.COPTCLOSE) throw Error("Illegal end of response type in service "+svc["name"]+"#"+name+" at line "+this.tn.line+": "+token); token = this.tn.next(); if (token === Lang.OPEN) { do { token = this.tn.next(); if (token === 'option') this._parseOption(method, token); // <- will fail for the custom-options example else if (token !== Lang.CLOSE) throw Error("Illegal start of option inservice "+svc["name"]+"#"+name+" at line "+this.tn.line+": "+token); } while (token !== Lang.CLOSE); if (this.tn.peek() === Lang.END) this.tn.next(); } else if (token !== Lang.END) throw Error("Illegal delimiter in service "+svc["name"]+"#"+name+" at line "+this.tn.line+": "+token); if (typeof svc[type] === 'undefined') svc[type] = {}; svc[type][name] = method; }; /** * Parses a message definition. * @param {Object} parent Parent definition * @param {Object} fld Field definition if this is a group, otherwise `null` * @param {string} token First token * @return {Object} * @throws {Error} If the message cannot be parsed * @private */ ParserPrototype._parseMessage = function(parent, fld, token) { /** @dict */ var msg = {}; // Note: At some point we might want to exclude the parser, so we need a dict. var isGroup = token === "group"; token = this.tn.next(); if (!Lang.NAME.test(token)) throw Error("Illegal "+(isGroup ? "group" : "message")+" name"+(parent ? " in message "+parent["name"] : "")+" at line "+this.tn.line+": "+token); msg["name"] = token; if (isGroup) { token = this.tn.next(); if (token !== Lang.EQUAL) throw Error("Illegal id assignment after group "+msg.name+" at line "+this.tn.line+": "+token); token = this.tn.next(); try { fld["id"] = this._parseId(token); } catch (e) { throw Error("Illegal field id value for group "+msg.name+"#"+fld.name+" at line "+this.tn.line+": "+token); } msg["isGroup"] = true; } msg["fields"] = []; // Note: Using arrays to support also browser that cannot preserve order of object keys. msg["enums"] = []; msg["messages"] = []; msg["options"] = {}; msg["oneofs"] = {}; token = this.tn.next(); if (token === Lang.OPTOPEN && fld) this._parseFieldOptions(msg, fld, token), token = this.tn.next(); if (token !== Lang.OPEN) throw Error("Illegal start of "+(isGroup ? "group" : "message")+" "+msg.name+" at line "+this.tn.line+": "+token); // msg["extensions"] = undefined do { token = this.tn.next(); if (token === Lang.CLOSE) { token = this.tn.peek(); if (token === Lang.END) this.tn.next(); break; } else if (Lang.RULE.test(token)) this._parseMessageField(msg, token); else if (token === "oneof") this._parseMessageOneOf(msg, token); else if (token === "enum") this._parseEnum(msg, token); else if (token === "message") this._parseMessage(msg, null, token); else if (token === "option") this._parseOption(msg, token); else if (token === "extensions") msg["extensions"] = this._parseExtensions(msg, token); else if (token === "extend") this._parseExtend(msg, token); else throw Error("Illegal token in message "+msg.name+" at line "+this.tn.line+": "+token); } while (true); parent["messages"].push(msg); return msg; }; /** * Parses a message field. * @param {Object} msg Message definition * @param {string} token Initial token * @returns {!Object} Field descriptor * @throws {Error} If the message field cannot be parsed * @private */ ParserPrototype._parseMessageField = function(msg, token) { /** @dict */ var fld = {}, grp = null; fld["rule"] = token; /** @dict */ fld["options"] = {}; token = this.tn.next(); if (token === "group") { // "A [legacy] group simply combines a nested message type and a field into a single declaration. In your // code, you can treat this message just as if it had a Result type field called result (the latter name is // converted to lower-case so that it does not conflict with the former)." grp = this._parseMessage(msg, fld, token); if (!/^[A-Z]/.test(grp["name"])) throw Error('Group names must start with a capital letter'); fld["type"] = grp["name"]; fld["name"] = grp["name"].toLowerCase(); token = this.tn.peek(); if (token === Lang.END) this.tn.next(); } else { if (!Lang.TYPE.test(token) && !Lang.TYPEREF.test(token)) throw Error("Illegal field type in message "+msg.name+" at line "+this.tn.line+": "+token); fld["type"] = token; token = this.tn.next(); if (!Lang.NAME.test(token)) throw Error("Illegal field name in message "+msg.name+" at line "+this.tn.line+": "+token); fld["name"] = token; token = this.tn.next(); if (token !== Lang.EQUAL) throw Error("Illegal token in field "+msg.name+"#"+fld.name+" at line "+this.tn.line+": "+token); token = this.tn.next(); try { fld["id"] = this._parseId(token); } catch (e) { throw Error("Illegal field id in message "+msg.name+"#"+fld.name+" at line "+this.tn.line+": "+token); } token = this.tn.next(); if (token === Lang.OPTOPEN) this._parseFieldOptions(msg, fld, token), token = this.tn.next(); if (token !== Lang.END) throw Error("Illegal delimiter in message "+msg.name+"#"+fld.name+" at line "+this.tn.line+": "+token); } msg["fields"].push(fld); return fld; }; /** * Parses a message oneof. * @param {Object} msg Message definition * @param {string} token Initial token * @throws {Error} If the message oneof cannot be parsed * @private */ ParserPrototype._parseMessageOneOf = function(msg, token) { token = this.tn.next(); if (!Lang.NAME.test(token)) throw Error("Illegal oneof name in message "+msg.name+" at line "+this.tn.line+": "+token); var name = token, fld; var fields = []; token = this.tn.next(); if (token !== Lang.OPEN) throw Error("Illegal start of oneof "+name+" at line "+this.tn.line+": "+token); while (this.tn.peek() !== Lang.CLOSE) { fld = this._parseMessageField(msg, "optional"); fld["oneof"] = name; fields.push(fld["id"]); } this.tn.next(); msg["oneofs"][name] = fields; }; /** * Parses a set of field option definitions. * @param {Object} msg Message definition * @param {Object} fld Field definition * @param {string} token Initial token * @throws {Error} If the message field options cannot be parsed * @private */ ParserPrototype._parseFieldOptions = function(msg, fld, token) { var first = true; do { token = this.tn.next(); if (token === Lang.OPTCLOSE) break; else if (token === Lang.OPTEND) { if (first) throw Error("Illegal start of options in message "+msg.name+"#"+fld.name+" at line "+this.tn.line+": "+token); token = this.tn.next(); } this._parseFieldOption(msg, fld, token); first = false; } while (true); }; /** * Parses a single field option. * @param {Object} msg Message definition * @param {Object} fld Field definition * @param {string} token Initial token * @throws {Error} If the mesage field option cannot be parsed * @private */ ParserPrototype._parseFieldOption = function(msg, fld, token) { var custom = false; if (token === Lang.COPTOPEN) token = this.tn.next(), custom = true; if (!Lang.TYPEREF.test(token)) throw Error("Illegal field option in "+msg.name+"#"+fld.name+" at line "+this.tn.line+": "+token); var name = token; token = this.tn.next(); if (custom) { if (token !== Lang.COPTCLOSE) throw Error("Illegal delimiter in "+msg.name+"#"+fld.name+" at line "+this.tn.line+": "+token); name = '('+name+')'; token = this.tn.next(); if (Lang.FQTYPEREF.test(token)) name += token, token = this.tn.next(); } if (token !== Lang.EQUAL) throw Error("Illegal token in "+msg.name+"#"+fld.name+" at line "+this.tn.line+": "+token); var value; token = this.tn.peek(); if (token === Lang.STRINGOPEN || token === Lang.STRINGOPEN_SQ) { value = this._parseString(); } else if (Lang.NUMBER.test(token, true)) value = this._parseNumber(this.tn.next(), true); else if (Lang.BOOL.test(token)) value = this.tn.next().toLowerCase() === 'true'; else if (Lang.TYPEREF.test(token)) value = this.tn.next(); // TODO: Resolve? else throw Error("Illegal value in message "+msg.name+"#"+fld.name+", option "+name+" at line "+this.tn.line+": "+token); fld["options"][name] = value; }; /** * Parses an enum. * @param {Object} msg Message definition * @param {string} token Initial token * @throws {Error} If the enum cannot be parsed * @private */ ParserPrototype._parseEnum = function(msg, token) { /** @dict */ var enm = {}; token = this.tn.next(); if (!Lang.NAME.test(token)) throw Error("Illegal enum name in message "+msg.name+" at line "+this.tn.line+": "+token); enm["name"] = token; token = this.tn.next(); if (token !== Lang.OPEN) throw Error("Illegal start of enum "+enm.name+" at line "+this.tn.line+": "+token); enm["values"] = []; enm["options"] = {}; do { token = this.tn.next(); if (token === Lang.CLOSE) { token = this.tn.peek(); if (token === Lang.END) this.tn.next(); break; } if (token == 'option') this._parseOption(enm, token); else { if (!Lang.NAME.test(token)) throw Error("Illegal name in enum "+enm.name+" at line "+this.tn.line+": "+token); this._parseEnumValue(enm, token); } } while (true); msg["enums"].push(enm); }; /** * Parses an enum value. * @param {Object} enm Enum definition * @param {string} token Initial token * @throws {Error} If the enum value cannot be parsed * @private */ ParserPrototype._parseEnumValue = function(enm, token) { /** @dict */ var val = {}; val["name"] = token; token = this.tn.next(); if (token !== Lang.EQUAL) throw Error("Illegal token in enum "+enm.name+" at line "+this.tn.line+": "+token); token = this.tn.next(); try { val["id"] = this._parseId(token, true); } catch (e) { throw Error("Illegal id in enum "+enm.name+" at line "+this.tn.line+": "+token); } enm["values"].push(val); token = this.tn.next(); if (token === Lang.OPTOPEN) { var opt = { 'options' : {} }; // TODO: Actually expose them somehow. this._parseFieldOptions(enm, opt, token); token = this.tn.next(); } if (token !== Lang.END) throw Error("Illegal delimiter in enum "+enm.name+" at line "+this.tn.line+": "+token); }; /** * Parses an extensions statement. * @param {Object} msg Message object * @param {string} token Initial token * @throws {Error} If the extensions statement cannot be parsed * @private */ ParserPrototype._parseExtensions = function(msg, token) { /** @type {Array.} */ var range = []; token = this.tn.next(); if (token === "min") // FIXME: Does the official implementation support this? range.push(ProtoBuf.ID_MIN); else if (token === "max") range.push(ProtoBuf.ID_MAX); else range.push(this._parseNumber(token)); token = this.tn.next(); if (token !== 'to') throw Error("Illegal extensions delimiter in message "+msg.name+" at line "+this.tn.line+": "+token); token = this.tn.next(); if (token === "min") range.push(ProtoBuf.ID_MIN); else if (token === "max") range.push(ProtoBuf.ID_MAX); else range.push(this._parseNumber(token)); token = this.tn.next(); if (token !== Lang.END) throw Error("Illegal extensions delimiter in message "+msg.name+" at line "+this.tn.line+": "+token); return range; }; /** * Parses an extend block. * @param {Object} parent Parent object * @param {string} token Initial token * @throws {Error} If the extend block cannot be parsed * @private */ ParserPrototype._parseExtend = function(parent, token) { token = this.tn.next(); if (!Lang.TYPEREF.test(token)) throw Error("Illegal message name at line "+this.tn.line+": "+token); /** @dict */ var ext = {}; ext["ref"] = token; ext["fields"] = []; token = this.tn.next(); if (token !== Lang.OPEN) throw Error("Illegal start of extend "+ext.name+" at line "+this.tn.line+": "+token); do { token = this.tn.next(); if (token === Lang.CLOSE) { token = this.tn.peek(); if (token == Lang.END) this.tn.next(); break; } else if (Lang.RULE.test(token)) this._parseMessageField(ext, token); else throw Error("Illegal token in extend "+ext.name+" at line "+this.tn.line+": "+token); } while (true); parent["messages"].push(ext); return ext; }; /** * Returns a string representation of this object. * @returns {string} String representation as of "Parser" */ ParserPrototype.toString = function() { return "Parser"; }; /** * @alias ProtoBuf.DotProto.Parser * @expose */ DotProto.Parser = Parser; return DotProto; })(ProtoBuf, ProtoBuf.Lang); /** * @alias ProtoBuf.Reflect * @expose */ ProtoBuf.Reflect = (function(ProtoBuf) { "use strict"; /** * Reflection types. * @exports ProtoBuf.Reflect * @namespace */ var Reflect = {}; /** * Constructs a Reflect base class. * @exports ProtoBuf.Reflect.T * @constructor * @abstract * @param {!ProtoBuf.Builder} builder Builder reference * @param {?ProtoBuf.Reflect.T} parent Parent object * @param {string} name Object name */ var T = function(builder, parent, name) { /** * Builder reference. * @type {!ProtoBuf.Builder} * @expose */ this.builder = builder; /** * Parent object. * @type {?ProtoBuf.Reflect.T} * @expose */ this.parent = parent; /** * Object name in namespace. * @type {string} * @expose */ this.name = name; /** * Fully qualified class name * @type {string} * @expose */ this.className; }; /** * @alias ProtoBuf.Reflect.T.prototype * @inner */ var TPrototype = T.prototype; /** * Returns the fully qualified name of this object. * @returns {string} Fully qualified name as of ".PATH.TO.THIS" * @expose */ TPrototype.fqn = function() { var name = this.name, ptr = this; do { ptr = ptr.parent; if (ptr == null) break; name = ptr.name+"."+name; } while (true); return name; }; /** * Returns a string representation of this Reflect object (its fully qualified name). * @param {boolean=} includeClass Set to true to include the class name. Defaults to false. * @return String representation * @expose */ TPrototype.toString = function(includeClass) { return (includeClass ? this.className + " " : "") + this.fqn(); }; /** * Builds this type. * @throws {Error} If this type cannot be built directly * @expose */ TPrototype.build = function() { throw Error(this.toString(true)+" cannot be built directly"); }; /** * @alias ProtoBuf.Reflect.T * @expose */ Reflect.T = T; /** * Constructs a new Namespace. * @exports ProtoBuf.Reflect.Namespace * @param {!ProtoBuf.Builder} builder Builder reference * @param {?ProtoBuf.Reflect.Namespace} parent Namespace parent * @param {string} name Namespace name * @param {Object.=} options Namespace options * @constructor * @extends ProtoBuf.Reflect.T */ var Namespace = function(builder, parent, name, options) { T.call(this, builder, parent, name); /** * @override */ this.className = "Namespace"; /** * Children inside the namespace. * @type {!Array.} */ this.children = []; /** * Options. * @type {!Object.} */ this.options = options || {}; }; /** * @alias ProtoBuf.Reflect.Namespace.prototype * @inner */ var NamespacePrototype = Namespace.prototype = Object.create(T.prototype); /** * Returns an array of the namespace's children. * @param {ProtoBuf.Reflect.T=} type Filter type (returns instances of this type only). Defaults to null (all children). * @return {Array.} * @expose */ NamespacePrototype.getChildren = function(type) { type = type || null; if (type == null) return this.children.slice(); var children = []; for (var i=0, k=this.children.length; i} Runtime namespace * @expose */ NamespacePrototype.build = function() { /** @dict */ var ns = {}; var children = this.children; for (var i=0, k=children.length, child; i} */ NamespacePrototype.buildOpt = function() { var opt = {}, keys = Object.keys(this.options); for (var i=0, k=keys.length; i}null} Option value or NULL if there is no such option */ NamespacePrototype.getOption = function(name) { if (typeof name === 'undefined') return this.options; return typeof this.options[name] !== 'undefined' ? this.options[name] : null; }; /** * @alias ProtoBuf.Reflect.Namespace * @expose */ Reflect.Namespace = Namespace; /** * 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.=} options Message options * @param {boolean=} isGroup `true` if this is a legacy group * @constructor * @extends ProtoBuf.Reflect.Namespace */ var Message = function(builder, parent, name, options, isGroup) { Namespace.call(this, builder, parent, name, options); /** * @override */ this.className = "Message"; /** * Extensions range. * @type {!Array.} * @expose */ this.extensions = [ProtoBuf.ID_MIN, ProtoBuf.ID_MAX]; /** * 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.} * @private */ this._fields = null; /** * Cached fields by id. * @type {?Object.} * @private */ this._fieldsById = null; /** * Cached fields by name. * @type {?Object.} * @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) { var fields = T.getChildren(ProtoBuf.Reflect.Message.Field), oneofs = T.getChildren(ProtoBuf.Reflect.Message.OneOf); /** * Constructs a new runtime Message. * @name ProtoBuf.Builder.Message * @class Barebone of all runtime messages. * @param {!Object.|string} values Preset values * @param {...string} var_args * @constructor * @throws {Error} If the message cannot be created */ var Message = function(values, var_args) { ProtoBuf.Builder.Message.call(this); // Create virtual oneof properties for (var i=0, k=oneofs.length; i 0) { // Set field values from a values object if (arguments.length === 1 && typeof values === 'object' && /* not another Message */ typeof values.encode !== 'function' && /* not a repeated field */ !ProtoBuf.Util.isArray(values) && /* not a ByteBuffer */ !(values instanceof ByteBuffer) && /* not an ArrayBuffer */ !(values instanceof ArrayBuffer) && /* not a Long */ !(ProtoBuf.Long && values instanceof ProtoBuf.Long)) { var keys = Object.keys(values); for (i=0, k=keys.length; i} Raw payload * @expose */ MessagePrototype.toRaw = function(includeBinaryAsBase64) { return cloneRaw(this, !!includeBinaryAsBase64); }; /** * Decodes a message from the specified buffer or string. * @name ProtoBuf.Builder.Message.decode * @function * @param {!ByteBuffer|!ArrayBuffer|!Buffer|string} buffer Buffer to decode from * @param {string=} enc Encoding if buffer is a string: hex, utf8 (not recommended), defaults to base64 * @return {!ProtoBuf.Builder.Message} Decoded message * @throws {Error} If the message cannot be decoded or if required fields are missing. The later still * returns the decoded message with missing fields in the `decoded` property on the error. * @expose * @see ProtoBuf.Builder.Message.decode64 * @see ProtoBuf.Builder.Message.decodeHex */ Message.decode = function(buffer, enc) { if (typeof buffer === 'string') buffer = ByteBuffer.wrap(buffer, enc ? enc : "base64"); buffer = buffer instanceof ByteBuffer ? buffer : ByteBuffer.wrap(buffer); // May throw var le = buffer.littleEndian; try { var msg = T.decode(buffer.LE()); buffer.LE(le); return msg; } catch (e) { buffer.LE(le); throw(e); } }; /** * Decodes a varint32 length-delimited message from the specified buffer or string. * @name ProtoBuf.Builder.Message.decodeDelimited * @function * @param {!ByteBuffer|!ArrayBuffer|!Buffer|string} buffer Buffer to decode from * @param {string=} enc Encoding if buffer is a string: hex, utf8 (not recommended), defaults to base64 * @return {ProtoBuf.Builder.Message} Decoded message or `null` if not enough bytes are available yet * @throws {Error} If the message cannot be decoded or if required fields are missing. The later still * returns the decoded message with missing fields in the `decoded` property on the error. * @expose */ Message.decodeDelimited = function(buffer, enc) { if (typeof buffer === 'string') buffer = ByteBuffer.wrap(buffer, enc ? enc : "base64"); buffer = buffer instanceof ByteBuffer ? buffer : ByteBuffer.wrap(buffer); // May throw if (buffer.remaining() < 1) return null; var off = buffer.offset, len = buffer.readVarint32(); if (buffer.remaining() < len) { buffer.offset = off; return null; } try { var msg = T.decode(buffer.slice(buffer.offset, buffer.offset + len).LE()); buffer.offset += len; return msg; } catch (err) { buffer.offset += len; throw err; } }; /** * Decodes the message from the specified base64 encoded string. * @name ProtoBuf.Builder.Message.decode64 * @function * @param {string} str String to decode from * @return {!ProtoBuf.Builder.Message} Decoded message * @throws {Error} If the message cannot be decoded or if required fields are missing. The later still * returns the decoded message with missing fields in the `decoded` property on the error. * @expose */ Message.decode64 = function(str) { return Message.decode(str, "base64"); }; /** * Decodes the message from the specified hex encoded string. * @name ProtoBuf.Builder.Message.decodeHex * @function * @param {string} str String to decode from * @return {!ProtoBuf.Builder.Message} Decoded message * @throws {Error} If the message cannot be decoded or if required fields are missing. The later still * returns the decoded message with missing fields in the `decoded` property on the error. * @expose */ Message.decodeHex = function(str) { return Message.decode(str, "hex"); }; // Utility /** * Returns a string representation of this Message. * @name ProtoBuf.Builder.Message#toString * @function * @return {string} String representation as of ".Fully.Qualified.MessageName" * @expose */ MessagePrototype.toString = function() { return T.toString(); }; // Properties /** * Options. * @name ProtoBuf.Builder.Message.$options * @type {Object.} * @expose */ var $options; // cc /** * Reflection type. * @name ProtoBuf.Builder.Message#$type * @type {!ProtoBuf.Reflect.Message} * @expose */ var $type; // cc if (Object.defineProperty) Object.defineProperty(Message, '$options', { "value": T.buildOpt() }), Object.defineProperty(MessagePrototype, "$type", { get: function() { return T; } }); 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> 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 the available 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) { length = 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 { msg[field.name] = field.decode(wireType, buffer); if (field.oneof) { if (this[field.oneof.name] !== null) this[this[field.oneof.name]] = null; msg[field.oneof.name] = field.name; } } } // 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=} options Options * @param {!ProtoBuf.Reflect.Message.OneOf=} oneof Enclosing OneOf * @constructor * @extends ProtoBuf.Reflect.T */ var Field = function(builder, message, rule, type, name, id, options, oneof) { T.call(this, builder, message, name); /** * @override */ this.className = "Message.Field"; /** * Message field required flag. * @type {boolean} * @expose */ this.required = rule === "required"; /** * Message field repeated flag. * @type {boolean} * @expose */ this.repeated = rule === "repeated"; /** * Message field type. Type reference string if unresolved, protobuf type if resolved. * @type {string|{name: string, wireType: number}} * @expose */ this.type = type; /** * Resolved type reference inside the global namespace. * @type {ProtoBuf.Reflect.T|null} * @expose */ this.resolvedType = null; /** * Unique message field id. * @type {number} * @expose */ this.id = id; /** * Message field options. * @type {!Object.} * @dict * @expose */ this.options = options || {}; /** * Default value. * @type {*} * @expose */ this.defaultValue = null; /** * Enclosing OneOf. * @type {?ProtoBuf.Reflect.Message.OneOf} * @expose */ this.oneof = oneof || null; /** * Original field name. * @type {string} * @expose */ this.originalName = this.name; // Used to revert camelcase transformation on naming collisions // Convert field names to camel case notation if the override is set if (this.builder.options['convertFieldsToCamelCase'] && !(this instanceof Message.ExtensionField)) this.name = Field._toCamelCase(this.name); }; /** * Converts a field name to camel case. * @param {string} name Likely underscore notated name * @returns {string} Camel case notated name * @private */ Field._toCamelCase = function(name) { return name.replace(/_([a-zA-Z])/g, function($0, $1) { return $1.toUpperCase(); }); }; /** * @alias ProtoBuf.Reflect.Message.Field.prototype * @inner */ var FieldPrototype = Field.prototype = Object.create(T.prototype); /** * Builds the field. * @override * @expose */ FieldPrototype.build = function() { this.defaultValue = typeof this.options['default'] !== 'undefined' ? this.verifyValue(this.options['default']) : null; }; /** * Makes a Long from a value. * @param {{low: number, high: number, unsigned: boolean}|string|number} value Value * @param {boolean=} unsigned Whether unsigned or not, defaults to reuse it from Long-like objects or to signed for * strings and numbers * @returns {!Long} * @throws {Error} If the value cannot be converted to a Long * @inner */ function mkLong(value, unsigned) { if (value && typeof value.low === 'number' && typeof value.high === 'number' && typeof value.unsigned === 'boolean' && value.low === value.low && value.high === value.high) return new ProtoBuf.Long(value.low, value.high, typeof unsigned === 'undefined' ? value.unsigned : unsigned); if (typeof value === 'string') return ProtoBuf.Long.fromString(value, unsigned || false, 10); if (typeof value === 'number') return ProtoBuf.Long.fromNumber(value, unsigned || false); throw Error("not convertible to Long"); } /** * Checks if the given value can be set for this field. * @param {*} value Value to check * @param {boolean=} skipRepeated Whether to skip the repeated value check or not. Defaults to false. * @return {*} Verified, maybe adjusted, value * @throws {Error} If the value cannot be set for this field * @expose */ FieldPrototype.verifyValue = function(value, skipRepeated) { skipRepeated = skipRepeated || false; var fail = function(val, msg) { throw Error("Illegal value for "+this.toString(true)+" of type "+this.type.name+": "+val+" ("+msg+")"); }.bind(this); if (value === null) { // NULL values for optional fields if (this.required) fail(typeof value, "required"); return null; } var i; if (this.repeated && !skipRepeated) { // Repeated values as arrays if (!ProtoBuf.Util.isArray(value)) value = [value]; var res = []; for (i=0; i 4294967295 ? value | 0 : value; // Unsigned 32bit case ProtoBuf.TYPES["uint32"]: case ProtoBuf.TYPES["fixed32"]: if (typeof value !== 'number' || (value === value && value % 1 !== 0)) fail(typeof value, "not an integer"); return value < 0 ? value >>> 0 : value; // Signed 64bit case ProtoBuf.TYPES["int64"]: case ProtoBuf.TYPES["sint64"]: case ProtoBuf.TYPES["sfixed64"]: { if (ProtoBuf.Long) try { return mkLong(value, false); } catch (e) { fail(typeof value, e.message); } else fail(typeof value, "requires Long.js"); } // Unsigned 64bit case ProtoBuf.TYPES["uint64"]: case ProtoBuf.TYPES["fixed64"]: { if (ProtoBuf.Long) try { return mkLong(value, true); } catch (e) { fail(typeof value, e.message); } else fail(typeof value, "requires Long.js"); } // Bool case ProtoBuf.TYPES["bool"]: if (typeof value !== 'boolean') fail(typeof value, "not a boolean"); return value; // Float case ProtoBuf.TYPES["float"]: case ProtoBuf.TYPES["double"]: if (typeof value !== 'number') fail(typeof value, "not a number"); return value; // Length-delimited string case ProtoBuf.TYPES["string"]: if (typeof value !== 'string' && !(value && value instanceof String)) fail(typeof value, "not a string"); return ""+value; // Convert String object to string // Length-delimited bytes case ProtoBuf.TYPES["bytes"]: if (ByteBuffer.isByteBuffer(value)) return value; return ByteBuffer.wrap(value, "base64"); // Constant enum value case ProtoBuf.TYPES["enum"]: { var values = this.resolvedType.getChildren(Enum.Value); for (i=0; i= 0) { // "All of the elements of the field are packed into a single key-value pair with wire type 2 // (length-delimited). Each element is encoded the same way it would be normally, except without a // tag preceding it." buffer.writeVarint32((this.id << 3) | ProtoBuf.WIRE_TYPES.LDELIM); buffer.ensureCapacity(buffer.offset += 1); // We do not know the length yet, so let's assume a varint of length 1 var start = buffer.offset; // Remember where the contents begin for (i=0; i 1) { // We need to move the contents var contents = buffer.slice(start, buffer.offset); start += varintLen-1; buffer.offset = start; buffer.append(contents); } buffer.writeVarint32(len, start-varintLen); } else { // "If your message definition has repeated elements (without the [packed=true] option), the encoded // message has zero or more key-value pairs with the same tag number" for (i=0; i= 0) { n += ByteBuffer.calculateVarint32((this.id << 3) | ProtoBuf.WIRE_TYPES.LDELIM); ni = 0; for (i=0; i= 0) { if (!skipRepeated) { nBytes = buffer.readVarint32(); nBytes = buffer.offset + nBytes; // Limit var values = []; while (buffer.offset < nBytes) values.push(this.decode(this.type.wireType, buffer, true)); return values; } // Read the next value otherwise... } switch (this.type) { // 32bit signed varint case ProtoBuf.TYPES["int32"]: return buffer.readVarint32() | 0; // 32bit unsigned varint case ProtoBuf.TYPES["uint32"]: return buffer.readVarint32() >>> 0; // 32bit signed varint zig-zag case ProtoBuf.TYPES["sint32"]: return buffer.readVarint32ZigZag() | 0; // Fixed 32bit unsigned case ProtoBuf.TYPES["fixed32"]: return buffer.readUint32() >>> 0; case ProtoBuf.TYPES["sfixed32"]: return buffer.readInt32() | 0; // 64bit signed varint case ProtoBuf.TYPES["int64"]: return buffer.readVarint64(); // 64bit unsigned varint case ProtoBuf.TYPES["uint64"]: return buffer.readVarint64().toUnsigned(); // 64bit signed varint zig-zag case ProtoBuf.TYPES["sint64"]: return buffer.readVarint64ZigZag(); // Fixed 64bit unsigned case ProtoBuf.TYPES["fixed64"]: return buffer.readUint64(); // Fixed 64bit signed case ProtoBuf.TYPES["sfixed64"]: return buffer.readInt64(); // Bool varint case ProtoBuf.TYPES["bool"]: return !!buffer.readVarint32(); // Constant enum value (varint) case ProtoBuf.TYPES["enum"]: // The following Builder.Message#set will already throw return buffer.readVarint32(); // 32bit float case ProtoBuf.TYPES["float"]: return buffer.readFloat(); // 64bit float case ProtoBuf.TYPES["double"]: return buffer.readDouble(); // Length-delimited string case ProtoBuf.TYPES["string"]: return buffer.readVString(); // Length-delimited bytes case ProtoBuf.TYPES["bytes"]: { nBytes = buffer.readVarint32(); if (buffer.remaining() < nBytes) throw Error("Illegal number of bytes for "+this.toString(true)+": "+nBytes+" required but got only "+buffer.remaining()); value = buffer.clone(); // Offset already set value.limit = value.offset+nBytes; buffer.offset += nBytes; return value; } // Length-delimited embedded message case ProtoBuf.TYPES["message"]: { nBytes = buffer.readVarint32(); return this.resolvedType.decode(buffer, nBytes); } // Legacy group case ProtoBuf.TYPES["group"]: return this.resolvedType.decode(buffer, -1, this.id); } // We should never end here throw Error("[INTERNAL] Illegal wire type for "+this.toString(true)+": "+wireType); }; /** * @alias ProtoBuf.Reflect.Message.Field * @expose */ Reflect.Message.Field = Field; /** * Constructs a new Message ExtensionField. * @exports ProtoBuf.Reflect.Message.ExtensionField * @param {!ProtoBuf.Builder} builder Builder reference * @param {!ProtoBuf.Reflect.Message} message Message reference * @param {string} rule Rule, one of requried, optional, repeated * @param {string} type Data type, e.g. int32 * @param {string} name Field name * @param {number} id Unique field id * @param {Object.=} options Options * @constructor * @extends ProtoBuf.Reflect.Message.Field */ var ExtensionField = function(builder, message, rule, type, name, id, options) { Field.call(this, builder, message, rule, type, name, id, options); /** * Extension reference. * @type {!ProtoBuf.Reflect.Extension} * @expose */ this.extension; }; // Extends Field ExtensionField.prototype = Object.create(Field.prototype); /** * @alias ProtoBuf.Reflect.Message.ExtensionField * @expose */ Reflect.Message.ExtensionField = ExtensionField; /** * Constructs a new Message OneOf. * @exports ProtoBuf.Reflect.Message.OneOf * @param {!ProtoBuf.Builder} builder Builder reference * @param {!ProtoBuf.Reflect.Message} message Message reference * @param {string} name OneOf name * @constructor * @extends ProtoBuf.Reflect.T */ var OneOf = function(builder, message, name) { T.call(this, builder, message, name); /** * Enclosed fields. * @type {!Array.} * @expose */ this.fields = []; }; /** * @alias ProtoBuf.Reflect.Message.OneOf * @expose */ Reflect.Message.OneOf = OneOf; /** * Constructs a new Enum. * @exports ProtoBuf.Reflect.Enum * @param {!ProtoBuf.Builder} builder Builder reference * @param {!ProtoBuf.Reflect.T} parent Parent Reflect object * @param {string} name Enum name * @param {Object.=} options Enum options * @constructor * @extends ProtoBuf.Reflect.Namespace */ var Enum = function(builder, parent, name, options) { Namespace.call(this, builder, parent, name, options); /** * @override */ this.className = "Enum"; /** * Runtime enum object. * @type {Object.|null} * @expose */ this.object = null; }; /** * @alias ProtoBuf.Reflect.Enum.prototype * @inner */ var EnumPrototype = Enum.prototype = Object.create(Namespace.prototype); /** * Builds this enum and returns the runtime counterpart. * @return {Object} * @expose */ EnumPrototype.build = function() { var enm = {}, values = this.getChildren(Enum.Value); for (var i=0, k=values.length; i=} options Options * @constructor * @extends ProtoBuf.Reflect.Namespace */ var Service = function(builder, root, name, options) { Namespace.call(this, builder, root, name, options); /** * @override */ this.className = "Service"; /** * Built runtime service class. * @type {?function(new:ProtoBuf.Builder.Service)} */ this.clazz = null; }; /** * @alias ProtoBuf.Reflect.Service.prototype * @inner */ var ServicePrototype = Service.prototype = Object.create(Namespace.prototype); /** * Builds the service and returns the runtime counterpart, which is a fully functional class. * @see ProtoBuf.Builder.Service * @param {boolean=} rebuild Whether to rebuild or not * @return {Function} Service class * @throws {Error} If the message cannot be built * @expose */ ServicePrototype.build = function(rebuild) { if (this.clazz && !rebuild) return this.clazz; // Create the runtime Service class in its own scope return this.clazz = (function(ProtoBuf, T) { /** * Constructs a new runtime Service. * @name ProtoBuf.Builder.Service * @param {function(string, ProtoBuf.Builder.Message, function(Error, ProtoBuf.Builder.Message=))=} rpcImpl RPC implementation receiving the method name and the message * @class Barebone of all runtime services. * @constructor * @throws {Error} If the service cannot be created */ var Service = function(rpcImpl) { ProtoBuf.Builder.Service.call(this); /** * Service implementation. * @name ProtoBuf.Builder.Service#rpcImpl * @type {!function(string, ProtoBuf.Builder.Message, function(Error, ProtoBuf.Builder.Message=))} * @expose */ this.rpcImpl = rpcImpl || function(name, msg, callback) { // This is what a user has to implement: A function receiving the method name, the actual message to // send (type checked) and the callback that's either provided with the error as its first // argument or null and the actual response message. setTimeout(callback.bind(this, Error("Not implemented, see: https://github.com/dcodeIO/ProtoBuf.js/wiki/Services")), 0); // Must be async! }; }; /** * @alias ProtoBuf.Builder.Service.prototype * @inner */ var ServicePrototype = Service.prototype = Object.create(ProtoBuf.Builder.Service.prototype); if (Object.defineProperty) Object.defineProperty(Service, "$options", { "value": T.buildOpt() }), Object.defineProperty(ServicePrototype, "$options", { "value": Service["$options"] }); /** * Asynchronously performs an RPC call using the given RPC implementation. * @name ProtoBuf.Builder.Service.[Method] * @function * @param {!function(string, ProtoBuf.Builder.Message, function(Error, ProtoBuf.Builder.Message=))} rpcImpl RPC implementation * @param {ProtoBuf.Builder.Message} req Request * @param {function(Error, (ProtoBuf.Builder.Message|ByteBuffer|Buffer|string)=)} callback Callback receiving * the error if any and the response either as a pre-parsed message or as its raw bytes * @abstract */ /** * Asynchronously performs an RPC call using the instance's RPC implementation. * @name ProtoBuf.Builder.Service#[Method] * @function * @param {ProtoBuf.Builder.Message} req Request * @param {function(Error, (ProtoBuf.Builder.Message|ByteBuffer|Buffer|string)=)} callback Callback receiving * the error if any and the response either as a pre-parsed message or as its raw bytes * @abstract */ var rpc = T.getChildren(ProtoBuf.Reflect.Service.RPCMethod); for (var i=0; i=} options Options * @constructor * @extends ProtoBuf.Reflect.T */ var Method = function(builder, svc, name, options) { T.call(this, builder, svc, name); /** * @override */ this.className = "Service.Method"; /** * Options. * @type {Object.} * @expose */ this.options = options || {}; }; /** * @alias ProtoBuf.Reflect.Service.Method.prototype * @inner */ var MethodPrototype = Method.prototype = Object.create(T.prototype); /** * Builds the method's '$options' property. * @name ProtoBuf.Reflect.Service.Method#buildOpt * @function * @return {Object.} */ MethodPrototype.buildOpt = NamespacePrototype.buildOpt; /** * @alias ProtoBuf.Reflect.Service.Method * @expose */ Reflect.Service.Method = Method; /** * RPC service method. * @exports ProtoBuf.Reflect.Service.RPCMethod * @param {!ProtoBuf.Builder} builder Builder reference * @param {!ProtoBuf.Reflect.Service} svc Service * @param {string} name Method name * @param {string} request Request message name * @param {string} response Response message name * @param {Object.=} options Options * @constructor * @extends ProtoBuf.Reflect.Service.Method */ var RPCMethod = function(builder, svc, name, request, response, options) { Method.call(this, builder, svc, name, options); /** * @override */ this.className = "Service.RPCMethod"; /** * Request message name. * @type {string} * @expose */ this.requestName = request; /** * Response message name. * @type {string} * @expose */ this.responseName = response; /** * Resolved request message type. * @type {ProtoBuf.Reflect.Message} * @expose */ this.resolvedRequestType = null; /** * Resolved response message type. * @type {ProtoBuf.Reflect.Message} * @expose */ this.resolvedResponseType = null; }; // Extends Method RPCMethod.prototype = Object.create(Method.prototype); /** * @alias ProtoBuf.Reflect.Service.RPCMethod * @expose */ Reflect.Service.RPCMethod = RPCMethod; return Reflect; })(ProtoBuf); /** * @alias ProtoBuf.Builder * @expose */ ProtoBuf.Builder = (function(ProtoBuf, Lang, Reflect) { "use strict"; /** * Constructs a new Builder. * @exports ProtoBuf.Builder * @class Provides the functionality to build protocol messages. * @param {Object.=} options Options * @constructor */ var Builder = function(options) { /** * Namespace. * @type {ProtoBuf.Reflect.Namespace} * @expose */ this.ns = new Reflect.Namespace(this, null, ""); // Global namespace /** * Namespace pointer. * @type {ProtoBuf.Reflect.T} * @expose */ this.ptr = this.ns; /** * Resolved flag. * @type {boolean} * @expose */ this.resolved = false; /** * The current building result. * @type {Object.|null} * @expose */ this.result = null; /** * Imported files. * @type {Array.} * @expose */ this.files = {}; /** * Import root override. * @type {?string} * @expose */ this.importRoot = null; /** * Options. * @type {!Object.} * @expose */ this.options = options || {}; }; /** * @alias ProtoBuf.Builder.prototype * @inner */ var BuilderPrototype = Builder.prototype; /** * Resets the pointer to the root namespace. * @expose */ BuilderPrototype.reset = function() { this.ptr = this.ns; }; /** * Defines a package on top of the current pointer position and places the pointer on it. * @param {string} pkg * @param {Object.=} options * @return {ProtoBuf.Builder} this * @throws {Error} If the package name is invalid * @expose */ BuilderPrototype.define = function(pkg, options) { if (typeof pkg !== 'string' || !Lang.TYPEREF.test(pkg)) throw Error("Illegal package: "+pkg); var part = pkg.split("."), i; for (i=0; i} def Definition * @return {boolean} true if valid, else false * @expose */ Builder.isValidMessage = function(def) { // Messages require a string name if (typeof def["name"] !== 'string' || !Lang.NAME.test(def["name"])) return false; // Messages must not contain values (that'd be an enum) or methods (that'd be a service) if (typeof def["values"] !== 'undefined' || typeof def["rpc"] !== 'undefined') return false; // Fields, enums and messages are arrays if provided var i; if (typeof def["fields"] !== 'undefined') { if (!ProtoBuf.Util.isArray(def["fields"])) return false; var ids = [], id; // IDs must be unique for (i=0; i= 0) return false; ids.push(id); } ids = null; } if (typeof def["enums"] !== 'undefined') { if (!ProtoBuf.Util.isArray(def["enums"])) return false; for (i=0; i var keys = Object.keys(def["options"]); for (var i=0, key; i>} defs Messages, enums or services to create * @return {ProtoBuf.Builder} this * @throws {Error} If a message definition is invalid * @expose */ BuilderPrototype.create = function(defs) { if (!defs) return this; // Nothing to create if (!ProtoBuf.Util.isArray(defs)) defs = [defs]; if (defs.length == 0) return this; // It's quite hard to keep track of scopes and memory here, so let's do this iteratively. var stack = []; stack.push(defs); // One level [a, b, c] while (stack.length > 0) { defs = stack.pop(); if (ProtoBuf.Util.isArray(defs)) { // Stack always contains entire namespaces while (defs.length > 0) { var def = defs.shift(); // Namespace always contains an array of messages, enums and services if (Builder.isValidMessage(def)) { var obj = new Reflect.Message(this, this.ptr, def["name"], def["options"], def["isGroup"]); // Create OneOfs var oneofs = {}; if (def["oneofs"]) { var keys = Object.keys(def["oneofs"]); for (var i=0, k=keys.length; i 0) { for (i=0, k=def["fields"].length; i 0) for (i=0; i 0) for (i=0; i ProtoBuf.ID_MAX) obj.extensions[1] = ProtoBuf.ID_MAX; } this.ptr.addChild(obj); // Add to current namespace if (subObj.length > 0) { stack.push(defs); // Push the current level back defs = subObj; // Continue processing sub level subObj = null; this.ptr = obj; // And move the pointer to this namespace obj = null; continue; } subObj = null; obj = null; } else if (Builder.isValidEnum(def)) { obj = new Reflect.Enum(this, this.ptr, def["name"], def["options"]); for (i=0; i obj.extensions[1]) throw Error("Illegal extended field id in message "+obj.name+": "+def['fields'][i]['id']+" ("+obj.extensions.join(' to ')+" expected)"); // Convert extension field names to camel case notation if the override is set var name = def["fields"][i]["name"]; if (this.options['convertFieldsToCamelCase']) name = Reflect.Message.Field._toCamelCase(def["fields"][i]["name"]); // see #161: Extensions use their fully qualified name as their runtime key and... fld = new Reflect.Message.ExtensionField(this, obj, def["fields"][i]["rule"], def["fields"][i]["type"], this.ptr.fqn()+'.'+name, def["fields"][i]["id"], def["fields"][i]["options"]); // ...are added on top of the current namespace as an extension which is used for // resolving their type later on (the extension always keeps the original name to // prevent naming collisions) var ext = new Reflect.Extension(this, this.ptr, def["fields"][i]["name"], fld); fld.extension = ext; this.ptr.addChild(ext); obj.addChild(fld); } } else if (!/\.?google\.protobuf\./.test(def["ref"])) // Silently skip internal extensions throw Error("Extended message "+def["ref"]+" is not defined"); } else throw Error("Not a valid definition: "+JSON.stringify(def)); def = null; } // Break goes here } else throw Error("Not a valid namespace: "+JSON.stringify(defs)); defs = null; this.ptr = this.ptr.parent; // This namespace is s done } this.resolved = false; // Require re-resolve this.result = null; // Require re-build return this; }; /** * Imports another definition into this builder. * @param {Object.} json Parsed import * @param {(string|{root: string, file: string})=} filename Imported file name * @return {ProtoBuf.Builder} this * @throws {Error} If the definition or file cannot be imported * @expose */ BuilderPrototype["import"] = function(json, filename) { if (typeof filename === 'string') { if (ProtoBuf.Util.IS_NODE) filename = require("path")['resolve'](filename); if (this.files[filename] === true) { this.reset(); return this; // Skip duplicate imports } this.files[filename] = true; } if (!!json['imports'] && json['imports'].length > 0) { var importRoot, delim = '/', resetRoot = false; if (typeof filename === 'object') { // If an import root is specified, override this.importRoot = filename["root"]; resetRoot = true; // ... and reset afterwards importRoot = this.importRoot; filename = filename["file"]; if (importRoot.indexOf("\\") >= 0 || filename.indexOf("\\") >= 0) delim = '\\'; } else if (typeof filename === 'string') { if (this.importRoot) // If import root is overridden, use it importRoot = this.importRoot; else { // Otherwise compute from filename if (filename.indexOf("/") >= 0) { // Unix importRoot = filename.replace(/\/[^\/]*$/, ""); if (/* /file.proto */ importRoot === "") importRoot = "/"; } else if (filename.indexOf("\\") >= 0) { // Windows importRoot = filename.replace(/\\[^\\]*$/, ""); delim = '\\'; } else importRoot = "."; } } else importRoot = null; for (var i=0; i= 0) return false; ids.push(id); } ids = null; } return true; }; /** * Resolves all namespace objects. * @throws {Error} If a type cannot be resolved * @expose */ BuilderPrototype.resolveAll = function() { // Resolve all reflected objects var res; if (this.ptr == null || typeof this.ptr.type === 'object') return; // Done (already resolved) if (this.ptr instanceof Reflect.Namespace) { // Build all children var children = this.ptr.children; for (var i= 0, k=children.length; i} * @throws {Error} If a type could not be resolved * @expose */ BuilderPrototype.build = function(path) { this.reset(); if (!this.resolved) this.resolveAll(), this.resolved = true, this.result = null; // Require re-build if (this.result == null) // (Re-)Build this.result = this.ns.build(); if (!path) return this.result; else { var part = path.split("."); var ptr = this.result; // Build namespace pointer (no hasChild etc.) for (var i=0; i=} options Builder options, defaults to global options set on ProtoBuf * @return {!ProtoBuf.Builder} Builder * @expose */ ProtoBuf.newBuilder = function(options) { options = options || {}; if (typeof options['convertFieldsToCamelCase'] === 'undefined') options['convertFieldsToCamelCase'] = ProtoBuf.convertFieldsToCamelCase; if (typeof options['populateAccessors'] === 'undefined') options['populateAccessors'] = ProtoBuf.populateAccessors; return new ProtoBuf.Builder(options); }; /** * Loads a .json definition and returns the Builder. * @param {!*|string} json JSON definition * @param {(ProtoBuf.Builder|string|{root: string, file: string})=} builder Builder to append to. Will create a new one if omitted. * @param {(string|{root: string, file: string})=} filename The corresponding file name if known. Must be specified for imports. * @return {ProtoBuf.Builder} Builder to create new messages * @throws {Error} If the definition cannot be parsed or built * @expose */ ProtoBuf.loadJson = function(json, builder, filename) { if (typeof builder === 'string' || (builder && typeof builder["file"] === 'string' && typeof builder["root"] === 'string')) filename = builder, builder = null; if (!builder || typeof builder !== 'object') builder = ProtoBuf.newBuilder(); if (typeof json === 'string') json = JSON.parse(json); builder["import"](json, filename); builder.resolveAll(); builder.build(); return builder; }; /** * Loads a .json file and returns the Builder. * @param {string|!{root: string, file: string}} filename Path to json file or an object specifying 'file' with * an overridden 'root' path for all imported files. * @param {function(?Error, !ProtoBuf.Builder=)=} callback Callback that will receive `null` as the first and * the Builder as its second argument on success, otherwise the error as its first argument. If omitted, the * file will be read synchronously and this function will return the Builder. * @param {ProtoBuf.Builder=} builder Builder to append to. Will create a new one if omitted. * @return {?ProtoBuf.Builder|undefined} The Builder if synchronous (no callback specified, will be NULL if the * request has failed), else undefined * @expose */ ProtoBuf.loadJsonFile = function(filename, callback, builder) { if (callback && typeof callback === 'object') builder = callback, callback = null; else if (!callback || typeof callback !== 'function') callback = null; if (callback) return ProtoBuf.Util.fetch(typeof filename === 'string' ? filename : filename["root"]+"/"+filename["file"], function(contents) { if (contents === null) { callback(Error("Failed to fetch file")); return; } try { callback(null, ProtoBuf.loadJson(JSON.parse(contents), builder, filename)); } catch (e) { callback(e); } }); var contents = ProtoBuf.Util.fetch(typeof filename === 'object' ? filename["root"]+"/"+filename["file"] : filename); return contents === null ? null : ProtoBuf.loadJson(JSON.parse(contents), builder, filename); }; return ProtoBuf; } /* CommonJS */ if (typeof require === 'function' && typeof module === 'object' && module && typeof exports === 'object' && exports) module['exports'] = init(require("bytebuffer")); /* AMD */ else if (typeof define === 'function' && define["amd"]) define(["ByteBuffer"], init); /* Global */ else (global["dcodeIO"] = global["dcodeIO"] || {})["ProtoBuf"] = init(global["dcodeIO"]["ByteBuffer"]); })(this);