| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| /** |
| * @fileoverview Protocol Buffer 2 Serializer which serializes and deserializes |
| * messages using the wire format. Note that this serializer requires protocol |
| * buffer reflection, which carries some overhead. |
| * @supported any browser with DataView implemented. For now Chrome9, FF15, IE10 |
| * |
| * @see https://developers.google.com/protocol-buffers/docs/encoding |
| * |
| * TODO(feinberg): Replace goog.math.Long with mutable long representation that |
| * permits in-place arithmetic to avoid allocations. |
| */ |
| |
| |
| goog.provide('net.proto2.contrib.WireSerializer'); |
| |
| goog.require('goog.array'); |
| goog.require('goog.asserts'); |
| goog.require('goog.math.Long'); |
| goog.require('goog.proto2.Message'); |
| goog.require('goog.proto2.Serializer'); |
| |
| |
| |
| /** |
| * Wire format serializer. |
| * |
| * @constructor |
| * @extends {goog.proto2.Serializer} |
| */ |
| net.proto2.contrib.WireSerializer = function() { |
| /** |
| * This array is where proto bytes go during serialization. |
| * It must be reset for each serialization. |
| * @type {!Array.<number>} |
| * @private |
| */ |
| this.buffer_ = []; |
| |
| /** |
| * Scratch workspace to avoid allocations during serialization. |
| * @type {{value: number, length: number}} |
| * @private |
| */ |
| this.scratchTag32_ = {value: 0, length: 0}; |
| |
| /** |
| * Scratch workspace to avoid allocations during serialization. |
| * @type {{value: !goog.math.Long, length: number}} |
| * @private |
| */ |
| this.scratchTag64_ = {value: goog.math.Long.getZero(), length: 0}; |
| |
| /** |
| * Scratch data view for coding/decoding little-endian numbers. |
| * @type {!DataView} |
| * @private |
| */ |
| this.dataView_ = new DataView(new ArrayBuffer(8)); |
| }; |
| goog.inherits(net.proto2.contrib.WireSerializer, goog.proto2.Serializer); |
| |
| |
| /** |
| * @return {!Array.<number>} The serialized form of the message. |
| * @override |
| */ |
| net.proto2.contrib.WireSerializer.prototype.serialize = function(message) { |
| if (message == null) { |
| return []; |
| } |
| |
| this.buffer_ = []; |
| |
| var descriptor = message.getDescriptor(); |
| var fields = descriptor.getFields(); |
| |
| // Add the known fields. |
| for (var i = 0; i < fields.length; i++) { |
| var field = fields[i]; |
| |
| if (!message.has(field)) { |
| continue; |
| } |
| |
| if (field.isRepeated()) { |
| if (field.isPacked()) { |
| this.serializePackedField_(message, field); |
| } else { |
| for (var j = 0, n = message.countOf(field); j < n; j++) { |
| var val = message.get(field, j); |
| this.getSerializedValue(field, val); |
| } |
| } |
| } else { |
| this.getSerializedValue(field, message.get(field)); |
| } |
| } |
| |
| return this.buffer_; |
| }; |
| |
| |
| /** |
| * Append the serialized packed field to our serialization buffer. |
| * @param {!goog.proto2.Message} message The message containing the field |
| * to serialize. |
| * @param {!goog.proto2.FieldDescriptor} field The field to serialize. |
| * @return {boolean} Whether the field tag was serialized. |
| * @private |
| */ |
| net.proto2.contrib.WireSerializer.prototype.serializePackedField_ = |
| function(message, field) { |
| var buf = this.buffer_; |
| |
| var wireType = 2; // Per definition. |
| |
| // Tag. |
| this.serializeVarint_((field.getTag() << 3) | wireType); |
| |
| // Make note of the current buffer size. After serializing the repeated |
| // fields, splice the size header at the current position. |
| var savedBufferSize = buf.length; |
| for (var j = 0, n = message.countOf(field); j < n; j++) { |
| var val = message.get(field, j); |
| this.getSerializedValue(field, val, true /* omit tag */); |
| } |
| var serializedData = buf.splice( |
| savedBufferSize, buf.length - savedBufferSize); |
| this.serializeVarint_(serializedData.length); |
| |
| var args = [buf.length, 0].concat(serializedData); |
| buf.splice.apply(buf, args); |
| |
| return true; |
| }; |
| |
| |
| /** |
| * Append the serialized field tag to our serialization buffer. |
| * @param {goog.proto2.FieldDescriptor} field The field to serialize. |
| * @return {boolean} Whether the field tag was serialized. |
| * @private |
| */ |
| net.proto2.contrib.WireSerializer.prototype.serializeFieldTag_ = |
| function(field) { |
| var wireType = 0; |
| switch (field.getFieldType()) { |
| default: |
| return false; |
| case goog.proto2.Message.FieldType.SINT32: |
| case goog.proto2.Message.FieldType.SINT64: |
| case goog.proto2.Message.FieldType.BOOL: |
| case goog.proto2.Message.FieldType.INT64: |
| case goog.proto2.Message.FieldType.ENUM: |
| case goog.proto2.Message.FieldType.INT32: |
| case goog.proto2.Message.FieldType.UINT32: |
| case goog.proto2.Message.FieldType.UINT64: |
| wireType = 0; |
| break; |
| case goog.proto2.Message.FieldType.FIXED64: |
| case goog.proto2.Message.FieldType.SFIXED64: |
| case goog.proto2.Message.FieldType.DOUBLE: |
| wireType = 1; |
| break; |
| case goog.proto2.Message.FieldType.STRING: |
| case goog.proto2.Message.FieldType.BYTES: |
| case goog.proto2.Message.FieldType.MESSAGE: |
| wireType = 2; |
| break; |
| case goog.proto2.Message.FieldType.GROUP: |
| wireType = 3; |
| break; |
| case goog.proto2.Message.FieldType.FIXED32: |
| case goog.proto2.Message.FieldType.SFIXED32: |
| case goog.proto2.Message.FieldType.FLOAT: |
| wireType = 5; |
| break; |
| } |
| this.serializeVarint_((field.getTag() << 3) | wireType); |
| return true; |
| }; |
| |
| |
| /** |
| * Returns the serialized form of the given value for the given field if the |
| * field is a Message or Group and returns the value unchanged otherwise, except |
| * for Infinity, -Infinity and NaN numerical values which are converted to |
| * string representation. |
| * |
| * @param {goog.proto2.FieldDescriptor} field The field from which this |
| * value came. |
| * @param {*} value The value of the field. |
| * @param {boolean=} opt_omitTag If present and true, do not serialize a field |
| * tag. |
| * |
| * @return {*} The value. |
| * @protected |
| */ |
| net.proto2.contrib.WireSerializer.prototype.getSerializedValue = |
| function(field, value, opt_omitTag) { |
| if (!opt_omitTag) { |
| if (!this.serializeFieldTag_(field)) { |
| return false; |
| } |
| } |
| |
| switch (field.getFieldType()) { |
| default: |
| throw new Error('Unknown field type ' + field.getFieldType()); |
| case goog.proto2.Message.FieldType.SINT32: |
| this.serializeVarint_(this.zigZagEncode(/** @type {number} */ (value))); |
| break; |
| case goog.proto2.Message.FieldType.SINT64: |
| this.serializeVarint64_(this.zigZagEncode64_( |
| goog.math.Long.fromString(/** @type {string} */(value)))); |
| break; |
| case goog.proto2.Message.FieldType.BOOL: |
| this.serializeVarint_(value ? 1 : 0); |
| break; |
| case goog.proto2.Message.FieldType.INT32: |
| var numericValue = /** @type {number} */ (value); |
| if (numericValue > 0) { |
| this.serializeVarint_(numericValue); |
| } else { |
| // Negative 32 bit quantities are always 10 bytes long. |
| this.serializeVarint64_(goog.math.Long.fromInt(numericValue)); |
| } |
| break; |
| case goog.proto2.Message.FieldType.INT64: |
| case goog.proto2.Message.FieldType.UINT64: |
| this.serializeVarint64_( |
| goog.math.Long.fromString(/** @type {string} */(value))); |
| break; |
| case goog.proto2.Message.FieldType.ENUM: |
| case goog.proto2.Message.FieldType.UINT32: |
| this.serializeVarint_(/** @type {number} */ (value)); |
| break; |
| case goog.proto2.Message.FieldType.FIXED64: |
| case goog.proto2.Message.FieldType.SFIXED64: |
| this.serializeFixed_( |
| goog.math.Long.fromString(/** @type {string} */ (value)), 8); |
| break; |
| case goog.proto2.Message.FieldType.DOUBLE: |
| this.serializeDouble_(/** @type {number} */ (value)); |
| break; |
| case goog.proto2.Message.FieldType.STRING: |
| this.serializeString(value); |
| break; |
| case goog.proto2.Message.FieldType.BYTES: |
| this.serializeBytes(value); |
| break; |
| case goog.proto2.Message.FieldType.GROUP: |
| var serialized = new net.proto2.contrib.WireSerializer().serialize( |
| /** @type {goog.proto2.Message} */ (value)); |
| goog.array.extend(this.buffer_, serialized); |
| this.serializeVarint_((field.getTag() << 3) | 4); |
| break; |
| case goog.proto2.Message.FieldType.MESSAGE: |
| var serialized = new net.proto2.contrib.WireSerializer().serialize( |
| /** @type {goog.proto2.Message} */ (value)); |
| this.serializeVarint_(serialized.length); |
| goog.array.extend(this.buffer_, serialized); |
| break; |
| case goog.proto2.Message.FieldType.FIXED32: |
| this.serializeFixed_( |
| goog.math.Long.fromNumber(/** @type {number} */ (value)), 4); |
| break; |
| case goog.proto2.Message.FieldType.SFIXED32: |
| this.serializeFixed_( |
| goog.math.Long.fromInt(/** @type {number} */ (value)), 4); |
| break; |
| case goog.proto2.Message.FieldType.FLOAT: |
| this.serializeFloat_(/** @type {number} */ (value)); |
| break; |
| } |
| // To avoid allocations, this method serializes into a pre-existing buffer, |
| // rather than serializing into a new value object. |
| return null; |
| }; |
| |
| |
| /** @override */ |
| net.proto2.contrib.WireSerializer.prototype.deserializeTo = |
| function(message, buffer) { |
| if (buffer == null) { |
| // Since value double-equals null, it may be either null or undefined. |
| // Ensure we return the same one, since they have different meanings. |
| return buffer; |
| } |
| |
| if (buffer instanceof ArrayBuffer) { |
| buffer = new Uint8Array(buffer); |
| } |
| |
| var descriptor = message.getDescriptor(); |
| var offset = 0; |
| var size = buffer.length; |
| var view = function() { |
| return buffer.subarray(offset); |
| }; |
| // Because subarray is broken on ie10, we can't simply advance our view of the |
| // buffer. Instead, we keep track of an offset. |
| while (offset < buffer.length) { |
| var tag = this.parseUnsignedVarInt_(view()); |
| var tagValue = tag.value; |
| var tagLength = tag.length; |
| var index = tagValue >> 3; |
| var wireType = tagValue & 0x7; // Last 3 bits. |
| |
| // Advance. |
| offset += tagLength; |
| |
| var field = descriptor.findFieldByTag(index); |
| if (!field) { |
| // Unknown field; skip it. |
| offset += this.lengthForWireType_(wireType, view()); |
| continue; |
| } else if (field.isPacked()) { // Packed repeated. |
| // Read byte length. |
| var v = this.parseUnsignedVarInt_(view()); |
| var remaining = v.value; |
| offset += v.length; |
| while (remaining > 0 && offset < buffer.length) { |
| var packedValue = |
| this.getDeserializedValue(field, view()); |
| if (!packedValue) { |
| throw new Error('Expected ' + field.getFieldType()); |
| } |
| message.add(field, packedValue.value); |
| offset += packedValue.length; |
| remaining -= packedValue.length; |
| } |
| } else { |
| var value = this.getDeserializedValue(field, view()); |
| if (!value) { |
| throw new Error('Expected ' + field.getFieldType()); |
| } |
| offset += value.length; |
| if (field.isRepeated()) { |
| message.add(field, value.value); |
| } else { |
| message.set(field, value.value); |
| } |
| } |
| } |
| }; |
| |
| |
| /** |
| * @param {number} wireType |
| * @param {*} buffer The data of the message. |
| * @return {number} Default length to use for a given fieldType. |
| * @private |
| */ |
| net.proto2.contrib.WireSerializer.prototype.lengthForWireType_ = function( |
| wireType, buffer) { |
| var length = 0; |
| switch (wireType) { |
| case 0: // int32, int64, uint32, uint64, sint32, sint64, bool, enum. |
| length = this.parseVarInt64_(buffer).length; |
| break; |
| case 1: // fixed64, sfixed64, double. |
| length = 8; |
| break; |
| case 2: // Length-delimited: string, bytes, messages, repeated fields. |
| var bufferLength = this.parseVarInt64_(buffer); |
| length = bufferLength.length + bufferLength.value.toInt(); |
| break; |
| case 3: // "Start group". Not supported. |
| case 4: // "End group". Not supported. |
| goog.asserts.fail('Error deserializing group'); |
| break; |
| case 5: // fixed32, sfixed32, float. |
| length = 4; |
| break; |
| } |
| return length; |
| }; |
| |
| |
| /** |
| * Deserializes a message from the expected format and places the |
| * data in the message. The message must correspond to a group. Moreover |
| * the buffer must be positioned after the initial START_GROUP tag for the |
| * group. The message will be terminated by the first END_GROUP tag at the |
| * same nesting level. It is the responsibility of the caller to validate that |
| * its field index matches the one in the opening START_GROUP tag. Since groups |
| * are not length-delimited, this method returns the length of the parsed |
| * data excluding the END_GROUP tag. |
| * |
| * @param {goog.proto2.Message} message The message in which to |
| * place the information. |
| * @param {*} buffer The data of the message. |
| * @return {number} the length of the parsed message, excluding the closing tag. |
| * @protected |
| */ |
| net.proto2.contrib.WireSerializer.prototype.deserializeGroupTo = |
| function(message, buffer) { |
| var descriptor = message.getDescriptor(); |
| var parsedLength = 0; |
| |
| while (true) { |
| var tag = this.parseUnsignedVarInt_(buffer); |
| var tagValue = tag.value; |
| var tagLength = tag.length; |
| var index = tagValue >> 3; |
| var wiretype = tagValue & 7; |
| if (wiretype == 4) { |
| // Got an end group. |
| break; |
| } |
| parsedLength += tagLength; |
| var value = {value: undefined, length: 0}; |
| var field = descriptor.findFieldByTag(index); |
| if (field) { |
| value = this.getDeserializedValue(field, buffer.subarray(tagLength)); |
| if (value && value.value !== null) { |
| if (field.isRepeated()) { |
| message.add(field, value.value); |
| } else { |
| message.set(field, value.value); |
| } |
| } |
| } |
| parsedLength += value.length; |
| if (buffer.length < tagLength + value.length) { |
| break; |
| } |
| buffer = buffer.subarray(tagLength + value.length); |
| } |
| return parsedLength; |
| }; |
| |
| |
| /** |
| * @override |
| */ |
| net.proto2.contrib.WireSerializer.prototype.getDeserializedValue = |
| function(field, buffer) { |
| var value = null; |
| var t = field.getFieldType(); |
| var varInt = this.parseVarInt64_(buffer); |
| var length = varInt.length; |
| switch (t) { |
| case goog.proto2.Message.FieldType.SINT32: |
| value = this.zigZagDecode_(varInt.value.toInt()); |
| break; |
| case goog.proto2.Message.FieldType.SINT64: |
| value = this.zigZagDecode64_(varInt.value).toString(); |
| break; |
| case goog.proto2.Message.FieldType.BOOL: |
| value = varInt.value.equals(goog.math.Long.getOne()); |
| break; |
| case goog.proto2.Message.FieldType.INT64: |
| case goog.proto2.Message.FieldType.UINT64: |
| value = varInt.value.toString(); |
| break; |
| case goog.proto2.Message.FieldType.INT32: |
| value = varInt.value.toInt(); |
| break; |
| case goog.proto2.Message.FieldType.ENUM: |
| case goog.proto2.Message.FieldType.UINT32: |
| value = varInt.value.getLowBitsUnsigned(); |
| break; |
| case goog.proto2.Message.FieldType.FIXED64: |
| case goog.proto2.Message.FieldType.SFIXED64: |
| value = this.parseFixed64_(buffer.subarray(0, 8)).toString(); |
| length = 8; |
| break; |
| case goog.proto2.Message.FieldType.DOUBLE: |
| value = this.parseDouble_(buffer.subarray(0, 8)); |
| length = 8; |
| break; |
| case goog.proto2.Message.FieldType.STRING: |
| var strBuffer = |
| buffer.subarray(varInt.length, varInt.length + varInt.value.toInt()); |
| value = this.arrayBufferToUtf8String_(strBuffer); |
| length = varInt.length + varInt.value.toInt(); |
| break; |
| case goog.proto2.Message.FieldType.BYTES: |
| var strBuffer = |
| buffer.subarray(varInt.length, varInt.length + varInt.value.toInt()); |
| // Store the bytes using a String. |
| value = this.arrayBufferToString_(strBuffer); |
| length = varInt.length + varInt.value.toInt(); |
| break; |
| case goog.proto2.Message.FieldType.GROUP: |
| value = field.getFieldMessageType().createMessageInstance(); |
| var groupLength = this.deserializeGroupTo(value, buffer); |
| var next = buffer.subarray(groupLength); |
| var closingTag = this.parseVarInt64_(next); |
| var expected = (field.getTag() << 3) | 4; |
| goog.asserts.assert(closingTag.value.toInt() == expected, |
| 'Error deserializing group'); |
| length = groupLength + closingTag.length; |
| break; |
| case goog.proto2.Message.FieldType.MESSAGE: |
| length = varInt.length + varInt.value.toInt(); |
| var data = buffer.subarray(varInt.length, length); |
| value = field.getFieldMessageType().createMessageInstance(); |
| this.deserializeTo(value, data); |
| break; |
| case goog.proto2.Message.FieldType.FIXED32: |
| case goog.proto2.Message.FieldType.SFIXED32: |
| value = this.parseFixed32_( |
| buffer.subarray(0, 4), t == goog.proto2.Message.FieldType.SFIXED32); |
| length = 4; |
| break; |
| case goog.proto2.Message.FieldType.FLOAT: |
| value = this.parseFloat_(buffer.subarray(0, 4)); |
| length = 4; |
| break; |
| } |
| return {value: value, length: length}; |
| }; |
| |
| |
| /** |
| * @param {*} value Binary string that needs to be converted to bytes. |
| */ |
| net.proto2.contrib.WireSerializer.prototype.serializeBytes = function(value) { |
| if (goog.isDefAndNotNull(value)) { |
| var valueStr = /** @type {string} */ (value); |
| // Serialize the number of bytes, per spec of the wire format. |
| this.serializeVarint_(valueStr.length); |
| for (var i = 0; i < valueStr.length; i++) { |
| this.buffer_.push(valueStr.charCodeAt(i)); |
| } |
| } |
| }; |
| |
| |
| /** |
| * @param {*} value String (possibly utf-8) that needs to be converted to bytes. |
| */ |
| net.proto2.contrib.WireSerializer.prototype.serializeString = function(value) { |
| if (goog.isDefAndNotNull(value)) { |
| var valueStr = /** @type {string} */ (value); |
| // Inspired by: |
| // http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html |
| var utf8 = unescape(encodeURIComponent(valueStr)); |
| // Serialize the length of the encoded string: what we want is the number |
| // of bytes, not the number of characters, per spec of the wire format. |
| this.serializeVarint_(utf8.length); |
| for (var i = 0; i < utf8.length; i++) { |
| this.buffer_.push(utf8.charCodeAt(i)); |
| } |
| } |
| }; |
| |
| |
| /** |
| * @param {*} buffer to parse as String. |
| * @return {{value: string, length: number}} |
| */ |
| net.proto2.contrib.WireSerializer.prototype.parseString = function(buffer) { |
| var length = this.parseUnsignedVarInt_(buffer); |
| var strBuffer = buffer.subarray(length.length, length.length + length.value); |
| return { |
| value: this.arrayBufferToUtf8String_(strBuffer), |
| length: length.length + length.value |
| }; |
| }; |
| |
| |
| /** |
| * @param {number} number signed number that needs to be converted to unsigned. |
| * @return {number} |
| */ |
| net.proto2.contrib.WireSerializer.prototype.zigZagEncode = |
| function(number) { |
| var sign = number >>> 31; |
| return (number << 1) ^ -sign; |
| }; |
| |
| |
| /** |
| * @param {number} number Unsigned number in zigzag format that needs |
| to be converted to signed. |
| * @return {number} signed. |
| * @private |
| */ |
| net.proto2.contrib.WireSerializer.prototype.zigZagDecode_ = |
| function(number) { |
| return (number >>> 1) ^ -(number & 1); |
| }; |
| |
| |
| /** |
| * @param {!goog.math.Long} number signed number that needs to be converted to |
| * unsigned. |
| * @return {!goog.math.Long} |
| * @private |
| */ |
| net.proto2.contrib.WireSerializer.prototype.zigZagEncode64_ = |
| function(number) { |
| var sign = number.shiftRightUnsigned(63); |
| return number.shiftLeft(1).xor(sign.negate()); |
| }; |
| |
| |
| /** |
| * @param {!goog.math.Long} number Unsigned number in zigzag format that needs |
| to be converted to signed. |
| * @return {!goog.math.Long} |
| * @private |
| */ |
| net.proto2.contrib.WireSerializer.prototype.zigZagDecode64_ = |
| function(number) { |
| return number.shiftRightUnsigned(1).xor( |
| number.and(goog.math.Long.getOne()).negate()); |
| }; |
| |
| |
| /** |
| * Serialize the given number as a varint into our buffer. |
| * @param {number} number that needs to be converted to varint. |
| * @private |
| */ |
| net.proto2.contrib.WireSerializer.prototype.serializeVarint_ = |
| function(number) { |
| do { |
| var chunk = number & 0x7F; |
| number = number >>> 7; |
| if (number > 0) { |
| chunk = chunk | 0x80; |
| } |
| this.buffer_.push(chunk); |
| } while (number > 0); |
| }; |
| |
| |
| /** |
| * Serialize the given 64-bit number as a varint into our buffer. |
| * @param {!goog.math.Long} number that needs to be encoded as varint. |
| * @private |
| */ |
| net.proto2.contrib.WireSerializer.prototype.serializeVarint64_ = |
| function(number) { |
| var mask = goog.math.Long.fromInt(0x7F); |
| do { |
| var chunk = number.and(mask).toInt(); |
| number = number.shiftRightUnsigned(7); |
| if (number.greaterThan(goog.math.Long.getZero())) { |
| chunk = chunk | 0x80; |
| } |
| this.buffer_.push(chunk); |
| } while (number.greaterThan(goog.math.Long.getZero())); |
| }; |
| |
| |
| /** |
| * @param {*} buffer from which field number and type needs to be extracted. |
| * @return {{value: !goog.math.Long, length: number}} |
| * @private |
| */ |
| net.proto2.contrib.WireSerializer.prototype.parseVarInt64_ = function(buffer) { |
| var valueInfo = this.scratchTag64_; |
| var number = goog.math.Long.fromNumber(0); |
| var i = 0; |
| for (; i < buffer.length; i++) { |
| var bits = goog.math.Long.fromInt(buffer[i] & 0x7F).shiftLeft(i * 7); |
| number = number.or(bits); |
| if ((buffer[i] & 0x80) == 0) { |
| break; |
| } |
| } |
| valueInfo.value = number; |
| valueInfo.length = i + 1; |
| return valueInfo; |
| }; |
| |
| |
| /** |
| * A special case parser for unsigned 32-bit varints, which can fit comfortably |
| * in 32 bits during decoding. |
| * @param {*} buffer from which field number and type needs to be extracted. |
| * @return {{value: number, length: number}} |
| * @private |
| */ |
| net.proto2.contrib.WireSerializer.prototype.parseUnsignedVarInt_ = |
| function(buffer) { |
| var valueInfo = this.scratchTag32_; |
| var result = 0; |
| var i = 0; |
| for (; i < buffer.length; i++) { |
| result = result | ((buffer[i] & 0x7F) << (i * 7)); |
| if ((buffer[i] & 0x80) == 0) { |
| break; |
| } |
| } |
| valueInfo.value = result; |
| valueInfo.length = i + 1; |
| return valueInfo; |
| }; |
| |
| |
| /** |
| * @param {goog.math.Long} number that needs to be converted to little endian |
| * order. |
| * @param {number} size of the result array (4 = 32bit, 8 = 64bit). |
| * @private |
| */ |
| net.proto2.contrib.WireSerializer.prototype.serializeFixed_ = |
| function(number, size) { |
| var mask = goog.math.Long.fromInt(0xFF); |
| for (var i = 0; i < size; i++) { |
| var chunk = number.and(mask).toInt(); |
| this.buffer_.push(chunk); |
| number = number.shiftRightUnsigned(8); |
| } |
| }; |
| |
| |
| /** |
| * @param {*} buffer from which the fixed32 value needs to be extracted. |
| * @param {boolean} signed if the fixed32 value represents a signed value |
| * (i.e. sfixed32). |
| * @return {number} |
| * @private |
| */ |
| net.proto2.contrib.WireSerializer.prototype.parseFixed32_ = function( |
| buffer, signed) { |
| var number = 0; |
| for (var i = 0; i < buffer.length; i++) { |
| number = number | (buffer[i] << (i * 8)); |
| } |
| if (!signed) { |
| // The bitwise operations above treat numbers as signed int32 values. |
| // Correct for this in the unsigned case by using >>> to coerce to unsigned. |
| number = number >>> 0; |
| } |
| return number; |
| }; |
| |
| |
| /** |
| * @param {*} buffer from which the fixed64 value needs to be extracted. |
| * @return {!goog.math.Long} |
| * @private |
| */ |
| net.proto2.contrib.WireSerializer.prototype.parseFixed64_ = function(buffer) { |
| // Javascript numbers are only accurate up to 51 bits as they are stored as |
| // 64-bit floating points. We store the result in a goog.math.Long object to |
| // preserve full precision. |
| return new goog.math.Long( |
| this.parseFixed32_(buffer.subarray(0, 4), true), |
| this.parseFixed32_(buffer.subarray(4, 8), true)); |
| }; |
| |
| |
| /** |
| * @param {*} buffer from which double needs to be extracted. |
| * @return {number} |
| * @private |
| */ |
| net.proto2.contrib.WireSerializer.prototype.parseDouble_ = function(buffer) { |
| for (var i = 0; i < 8; i++) { |
| this.dataView_.setUint8(i, buffer[i]); |
| } |
| return this.dataView_.getFloat64(0, true); // little-endian |
| }; |
| |
| |
| /** |
| * @param {*} buffer from which float needs to be extracted. |
| * @return {number} |
| * @private |
| */ |
| net.proto2.contrib.WireSerializer.prototype.parseFloat_ = function(buffer) { |
| for (var i = 0; i < 4; i++) { |
| this.dataView_.setUint8(i, buffer[i]); |
| } |
| return this.dataView_.getFloat32(0, true); // little-endian |
| }; |
| |
| |
| /** |
| * @param {number} number to be serialized to 8 bytes. |
| * @private |
| */ |
| net.proto2.contrib.WireSerializer.prototype.serializeDouble_ = |
| function(number) { |
| this.dataView_.setFloat64(0, number, true); // little-endian |
| for (var i = 0; i < 8; i++) { |
| this.buffer_.push(this.dataView_.getUint8(i)); |
| } |
| }; |
| |
| |
| /** |
| * @param {number} number to be serialized to 4 bytes. |
| * @private |
| */ |
| net.proto2.contrib.WireSerializer.prototype.serializeFloat_ = function(number) { |
| this.dataView_.setFloat32(0, number, true); // little-endian |
| for (var i = 0; i < 4; i++) { |
| this.buffer_.push(this.dataView_.getUint8(i)); |
| } |
| }; |
| |
| |
| /** |
| * This method converts an ArrayBuffer into a string (with utf8 encoding). |
| * |
| * @param {ArrayBuffer} buffer The buffer to convert to a string |
| * @return {string} |
| * @private |
| */ |
| net.proto2.contrib.WireSerializer.prototype.arrayBufferToUtf8String_ = function( |
| buffer) { |
| var str = this.arrayBufferToString_(buffer); |
| // Inspired by: |
| // http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html |
| return decodeURIComponent(escape(str)); |
| }; |
| |
| |
| /** |
| * This method converts an ArrayBuffer into a string (each index is 1 byte). |
| * |
| * The maximum stack size in chrome is ~125k. This means that using |
| * String.fromCharCode.apply will fail for strings larger than the maximum stack |
| * size. This method breaks up the calls to fromCharCode into ~64k chunks to |
| * work around this limitation. |
| * |
| * @param {ArrayBuffer} buffer The buffer to convert to a string |
| * @return {string} |
| * @private |
| */ |
| net.proto2.contrib.WireSerializer.prototype.arrayBufferToString_ = function( |
| buffer) { |
| var CHUNK_SIZE = 65536; |
| var str = ''; |
| var view = new Uint16Array(buffer); |
| for (var offset = 0; offset < view.length; offset += CHUNK_SIZE) { |
| var len = Math.min(CHUNK_SIZE, view.length - offset); |
| var subview = view.subarray(offset, offset + len); |
| str += String.fromCharCode.apply(null, subview); |
| } |
| return str; |
| }; |