diff options
author | Jisi Liu <jisi.liu@gmail.com> | 2016-03-30 11:39:59 -0700 |
---|---|---|
committer | Jisi Liu <jisi.liu@gmail.com> | 2016-03-30 11:39:59 -0700 |
commit | 3b3c8abb9635eb3ea078a821a99c9ef29d66dff7 (patch) | |
tree | 7d2ec154f15c9f9153d890e76b6cf30e471ea488 /js | |
parent | 78105897a8f01c7be9cf8502b6c58d47eb1ccdd7 (diff) |
Integrate google internal changes.
Diffstat (limited to 'js')
-rw-r--r-- | js/binary/constants.js | 46 | ||||
-rw-r--r-- | js/binary/decoder.js | 12 | ||||
-rw-r--r-- | js/binary/decoder_test.js | 120 | ||||
-rw-r--r-- | js/binary/encoder.js | 430 | ||||
-rw-r--r-- | js/binary/proto_test.js | 315 | ||||
-rw-r--r-- | js/binary/reader.js | 8 | ||||
-rw-r--r-- | js/binary/reader_test.js | 74 | ||||
-rw-r--r-- | js/binary/utils.js | 64 | ||||
-rw-r--r-- | js/binary/utils_test.js | 27 | ||||
-rw-r--r-- | js/binary/writer.js | 1205 | ||||
-rw-r--r-- | js/debug.js | 2 | ||||
-rw-r--r-- | js/debug_test.js | 1 | ||||
-rw-r--r-- | js/message.js | 284 | ||||
-rw-r--r-- | js/message_test.js | 50 | ||||
-rw-r--r-- | js/proto3_test.js | 89 | ||||
-rw-r--r-- | js/test.proto | 12 |
16 files changed, 1500 insertions, 1239 deletions
diff --git a/js/binary/constants.js b/js/binary/constants.js index a976e0b6..836216bf 100644 --- a/js/binary/constants.js +++ b/js/binary/constants.js @@ -41,11 +41,16 @@ goog.provide('jspb.BinaryMessage'); goog.provide('jspb.BuilderFunction'); goog.provide('jspb.ByteSource'); goog.provide('jspb.ClonerFunction'); +goog.provide('jspb.ComparerFunction'); goog.provide('jspb.ConstBinaryMessage'); +goog.provide('jspb.PrunerFunction'); goog.provide('jspb.ReaderFunction'); goog.provide('jspb.RecyclerFunction'); +goog.provide('jspb.RepeatedFieldType'); +goog.provide('jspb.ScalarFieldType'); goog.provide('jspb.WriterFunction'); + goog.forwardDeclare('jspb.Message'); goog.forwardDeclare('jsproto.BinaryExtension'); @@ -79,11 +84,29 @@ jspb.ByteSource; /** + * A scalar field in jspb can be a boolean, number, or string. + * @typedef {boolean|number|string} + */ +jspb.ScalarFieldType; + + +/** + * A repeated field in jspb is an array of scalars, blobs, or messages. + * @typedef {!Array<jspb.ScalarFieldType>| + !Array<!Uint8Array>| + !Array<!jspb.BinaryMessage>} + */ +jspb.RepeatedFieldType; + + +/** * A field in jspb can be a scalar, a block of bytes, another proto, or an * array of any of the above. - * @typedef {boolean|number|string|Uint8Array| - jspb.BinaryMessage|jsproto.BinaryExtension| - Array<jspb.AnyFieldType>} + * @typedef {jspb.ScalarFieldType| + jspb.RepeatedFieldType| + !Uint8Array| + !jspb.BinaryMessage| + !jsproto.BinaryExtension} */ jspb.AnyFieldType; @@ -125,6 +148,23 @@ jspb.WriterFunction; /** + * A pruner function removes default-valued fields and empty submessages from a + * message and returns either the pruned message or null if the entire message + * was pruned away. + * @typedef {function(?jspb.BinaryMessage):?jspb.BinaryMessage} + */ +jspb.PrunerFunction; + + +/** + * A comparer function returns true if two protos are equal. + * @typedef {!function(?jspb.ConstBinaryMessage, + * ?jspb.ConstBinaryMessage):boolean} + */ +jspb.ComparerFunction; + + +/** * Field type codes, taken from proto2/public/wire_format_lite.h. * @enum {number} */ diff --git a/js/binary/decoder.js b/js/binary/decoder.js index 9004eff0..41094a36 100644 --- a/js/binary/decoder.js +++ b/js/binary/decoder.js @@ -223,7 +223,7 @@ jspb.BinaryIterator.prototype.next = function() { jspb.BinaryDecoder = function(opt_bytes, opt_start, opt_length) { /** * Typed byte-wise view of the source buffer. - * @private {Uint8Array} + * @private {?Uint8Array} */ this.bytes_ = null; @@ -335,7 +335,7 @@ jspb.BinaryDecoder.prototype.clear = function() { /** * Returns the raw buffer. - * @return {Uint8Array} The raw buffer. + * @return {?Uint8Array} The raw buffer. */ jspb.BinaryDecoder.prototype.getBuffer = function() { return this.bytes_; @@ -631,6 +631,7 @@ jspb.BinaryDecoder.prototype.readUnsignedVarint32String = function() { return value.toString(); }; + /** * Reads a 32-bit signed variant and returns its value as a string. * @@ -950,14 +951,15 @@ jspb.BinaryDecoder.prototype.readStringWithLength = function() { * Reads a block of raw bytes from the binary stream. * * @param {number} length The number of bytes to read. - * @return {Uint8Array} The decoded block of bytes, or null if the length was - * invalid. + * @return {!Uint8Array} The decoded block of bytes, or an empty block if the + * length was invalid. */ jspb.BinaryDecoder.prototype.readBytes = function(length) { if (length < 0 || this.cursor_ + length > this.bytes_.length) { this.error_ = true; - return null; + goog.asserts.fail('Invalid byte length!'); + return new Uint8Array(0); } var result = this.bytes_.subarray(this.cursor_, this.cursor_ + length); diff --git a/js/binary/decoder_test.js b/js/binary/decoder_test.js index 27342e49..d045e912 100644 --- a/js/binary/decoder_test.js +++ b/js/binary/decoder_test.js @@ -44,11 +44,11 @@ goog.require('goog.testing.asserts'); goog.require('jspb.BinaryConstants'); goog.require('jspb.BinaryDecoder'); -goog.require('jspb.BinaryWriter'); +goog.require('jspb.BinaryEncoder'); /** - * Tests raw encoding and decoding of unsigned types. + * Tests encoding and decoding of unsigned types. * @param {Function} readValue * @param {Function} writeValue * @param {number} epsilon @@ -58,34 +58,38 @@ goog.require('jspb.BinaryWriter'); */ function doTestUnsignedValue(readValue, writeValue, epsilon, upperLimit, filter) { - var writer = new jspb.BinaryWriter(); + var encoder = new jspb.BinaryEncoder(); // Encode zero and limits. - writeValue.call(writer, filter(0)); - writeValue.call(writer, filter(epsilon)); - writeValue.call(writer, filter(upperLimit)); + writeValue.call(encoder, filter(0)); + writeValue.call(encoder, filter(epsilon)); + writeValue.call(encoder, filter(upperLimit)); // Encode positive values. for (var cursor = epsilon; cursor < upperLimit; cursor *= 1.1) { - writeValue.call(writer, filter(cursor)); + writeValue.call(encoder, filter(cursor)); } - var reader = jspb.BinaryDecoder.alloc(writer.getResultBuffer()); + var decoder = jspb.BinaryDecoder.alloc(encoder.end()); // Check zero and limits. - assertEquals(filter(0), readValue.call(reader)); - assertEquals(filter(epsilon), readValue.call(reader)); - assertEquals(filter(upperLimit), readValue.call(reader)); + assertEquals(filter(0), readValue.call(decoder)); + assertEquals(filter(epsilon), readValue.call(decoder)); + assertEquals(filter(upperLimit), readValue.call(decoder)); // Check positive values. for (var cursor = epsilon; cursor < upperLimit; cursor *= 1.1) { - if (filter(cursor) != readValue.call(reader)) throw 'fail!'; + if (filter(cursor) != readValue.call(decoder)) throw 'fail!'; } + + // Encoding values outside the valid range should assert. + assertThrows(function() {writeValue.call(encoder, -1);}); + assertThrows(function() {writeValue.call(encoder, upperLimit * 1.1);}); } /** - * Tests raw encoding and decoding of signed types. + * Tests encoding and decoding of signed types. * @param {Function} readValue * @param {Function} writeValue * @param {number} epsilon @@ -96,44 +100,48 @@ function doTestUnsignedValue(readValue, */ function doTestSignedValue(readValue, writeValue, epsilon, lowerLimit, upperLimit, filter) { - var writer = new jspb.BinaryWriter(); + var encoder = new jspb.BinaryEncoder(); // Encode zero and limits. - writeValue.call(writer, filter(lowerLimit)); - writeValue.call(writer, filter(-epsilon)); - writeValue.call(writer, filter(0)); - writeValue.call(writer, filter(epsilon)); - writeValue.call(writer, filter(upperLimit)); + writeValue.call(encoder, filter(lowerLimit)); + writeValue.call(encoder, filter(-epsilon)); + writeValue.call(encoder, filter(0)); + writeValue.call(encoder, filter(epsilon)); + writeValue.call(encoder, filter(upperLimit)); var inputValues = []; // Encode negative values. for (var cursor = lowerLimit; cursor < -epsilon; cursor /= 1.1) { var val = filter(cursor); - writeValue.call(writer, val); + writeValue.call(encoder, val); inputValues.push(val); } // Encode positive values. for (var cursor = epsilon; cursor < upperLimit; cursor *= 1.1) { var val = filter(cursor); - writeValue.call(writer, val); + writeValue.call(encoder, val); inputValues.push(val); } - var reader = jspb.BinaryDecoder.alloc(writer.getResultBuffer()); + var decoder = jspb.BinaryDecoder.alloc(encoder.end()); // Check zero and limits. - assertEquals(filter(lowerLimit), readValue.call(reader)); - assertEquals(filter(-epsilon), readValue.call(reader)); - assertEquals(filter(0), readValue.call(reader)); - assertEquals(filter(epsilon), readValue.call(reader)); - assertEquals(filter(upperLimit), readValue.call(reader)); + assertEquals(filter(lowerLimit), readValue.call(decoder)); + assertEquals(filter(-epsilon), readValue.call(decoder)); + assertEquals(filter(0), readValue.call(decoder)); + assertEquals(filter(epsilon), readValue.call(decoder)); + assertEquals(filter(upperLimit), readValue.call(decoder)); // Verify decoded values. for (var i = 0; i < inputValues.length; i++) { - assertEquals(inputValues[i], readValue.call(reader)); + assertEquals(inputValues[i], readValue.call(decoder)); } + + // Encoding values outside the valid range should assert. + assertThrows(function() {writeValue.call(encoder, lowerLimit * 1.1);}); + assertThrows(function() {writeValue.call(encoder, upperLimit * 1.1);}); } describe('binaryDecoderTest', function() { @@ -169,7 +177,7 @@ describe('binaryDecoderTest', function() { * Tests reading 64-bit integers as hash strings. */ it('testHashStrings', function() { - var writer = new jspb.BinaryWriter(); + var encoder = new jspb.BinaryEncoder(); var hashA = String.fromCharCode(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); @@ -180,17 +188,17 @@ describe('binaryDecoderTest', function() { var hashD = String.fromCharCode(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF); - writer.rawWriteVarintHash64(hashA); - writer.rawWriteVarintHash64(hashB); - writer.rawWriteVarintHash64(hashC); - writer.rawWriteVarintHash64(hashD); + encoder.writeVarintHash64(hashA); + encoder.writeVarintHash64(hashB); + encoder.writeVarintHash64(hashC); + encoder.writeVarintHash64(hashD); - writer.rawWriteFixedHash64(hashA); - writer.rawWriteFixedHash64(hashB); - writer.rawWriteFixedHash64(hashC); - writer.rawWriteFixedHash64(hashD); + encoder.writeFixedHash64(hashA); + encoder.writeFixedHash64(hashB); + encoder.writeFixedHash64(hashC); + encoder.writeFixedHash64(hashD); - var decoder = jspb.BinaryDecoder.alloc(writer.getResultBuffer()); + var decoder = jspb.BinaryDecoder.alloc(encoder.end()); assertEquals(hashA, decoder.readVarintHash64()); assertEquals(hashB, decoder.readVarintHash64()); @@ -214,8 +222,8 @@ describe('binaryDecoderTest', function() { assertThrows(function() {decoder.readUint64()}); // Overlong varints should trigger assertions. - decoder.setBlock( - [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0]); + decoder.setBlock([255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 0]); assertThrows(function() {decoder.readUnsignedVarint64()}); decoder.reset(); assertThrows(function() {decoder.readSignedVarint64()}); @@ -244,61 +252,61 @@ describe('binaryDecoderTest', function() { /** - * Tests raw encoding and decoding of unsigned integers. + * Tests encoding and decoding of unsigned integers. */ - it('testRawUnsigned', function() { + it('testUnsignedIntegers', function() { doTestUnsignedValue( jspb.BinaryDecoder.prototype.readUint8, - jspb.BinaryWriter.prototype.rawWriteUint8, + jspb.BinaryEncoder.prototype.writeUint8, 1, 0xFF, Math.round); doTestUnsignedValue( jspb.BinaryDecoder.prototype.readUint16, - jspb.BinaryWriter.prototype.rawWriteUint16, + jspb.BinaryEncoder.prototype.writeUint16, 1, 0xFFFF, Math.round); doTestUnsignedValue( jspb.BinaryDecoder.prototype.readUint32, - jspb.BinaryWriter.prototype.rawWriteUint32, + jspb.BinaryEncoder.prototype.writeUint32, 1, 0xFFFFFFFF, Math.round); doTestUnsignedValue( jspb.BinaryDecoder.prototype.readUint64, - jspb.BinaryWriter.prototype.rawWriteUint64, + jspb.BinaryEncoder.prototype.writeUint64, 1, Math.pow(2, 64) - 1025, Math.round); }); /** - * Tests raw encoding and decoding of signed integers. + * Tests encoding and decoding of signed integers. */ - it('testRawSigned', function() { + it('testSignedIntegers', function() { doTestSignedValue( jspb.BinaryDecoder.prototype.readInt8, - jspb.BinaryWriter.prototype.rawWriteInt8, + jspb.BinaryEncoder.prototype.writeInt8, 1, -0x80, 0x7F, Math.round); doTestSignedValue( jspb.BinaryDecoder.prototype.readInt16, - jspb.BinaryWriter.prototype.rawWriteInt16, + jspb.BinaryEncoder.prototype.writeInt16, 1, -0x8000, 0x7FFF, Math.round); doTestSignedValue( jspb.BinaryDecoder.prototype.readInt32, - jspb.BinaryWriter.prototype.rawWriteInt32, + jspb.BinaryEncoder.prototype.writeInt32, 1, -0x80000000, 0x7FFFFFFF, Math.round); doTestSignedValue( jspb.BinaryDecoder.prototype.readInt64, - jspb.BinaryWriter.prototype.rawWriteInt64, + jspb.BinaryEncoder.prototype.writeInt64, 1, -Math.pow(2, 63), Math.pow(2, 63) - 513, Math.round); }); /** - * Tests raw encoding and decoding of floats. + * Tests encoding and decoding of floats. */ - it('testRawFloats', function() { + it('testFloats', function() { /** * @param {number} x * @return {number} @@ -310,7 +318,7 @@ describe('binaryDecoderTest', function() { } doTestSignedValue( jspb.BinaryDecoder.prototype.readFloat, - jspb.BinaryWriter.prototype.rawWriteFloat, + jspb.BinaryEncoder.prototype.writeFloat, jspb.BinaryConstants.FLOAT32_EPS, -jspb.BinaryConstants.FLOAT32_MAX, jspb.BinaryConstants.FLOAT32_MAX, @@ -318,7 +326,7 @@ describe('binaryDecoderTest', function() { doTestSignedValue( jspb.BinaryDecoder.prototype.readDouble, - jspb.BinaryWriter.prototype.rawWriteDouble, + jspb.BinaryEncoder.prototype.writeDouble, jspb.BinaryConstants.FLOAT64_EPS * 10, -jspb.BinaryConstants.FLOAT64_MAX, jspb.BinaryConstants.FLOAT64_MAX, diff --git a/js/binary/encoder.js b/js/binary/encoder.js new file mode 100644 index 00000000..c9b0c2ae --- /dev/null +++ b/js/binary/encoder.js @@ -0,0 +1,430 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/** + * @fileoverview BinaryEncode defines methods for encoding Javascript values + * into arrays of bytes compatible with the Protocol Buffer wire format. + * + * @author aappleby@google.com (Austin Appleby) + */ + +goog.provide('jspb.BinaryEncoder'); + +goog.require('goog.asserts'); +goog.require('jspb.BinaryConstants'); +goog.require('jspb.utils'); + + + +/** + * BinaryEncoder implements encoders for all the wire types specified in + * https://developers.google.com/protocol-buffers/docs/encoding. + * + * @constructor + * @struct + */ +jspb.BinaryEncoder = function() { + /** @private {!Array.<number>} */ + this.buffer_ = []; +}; + + +/** + * @return {number} + */ +jspb.BinaryEncoder.prototype.length = function() { + return this.buffer_.length; +}; + + +/** + * @return {!Array.<number>} + */ +jspb.BinaryEncoder.prototype.end = function() { + var buffer = this.buffer_; + this.buffer_ = []; + return buffer; +}; + + +/** + * Encodes a 64-bit integer in 32:32 split representation into its wire-format + * varint representation and stores it in the buffer. + * @param {number} lowBits The low 32 bits of the int. + * @param {number} highBits The high 32 bits of the int. + */ +jspb.BinaryEncoder.prototype.writeSplitVarint64 = function(lowBits, highBits) { + goog.asserts.assert(lowBits == Math.floor(lowBits)); + goog.asserts.assert(highBits == Math.floor(highBits)); + goog.asserts.assert((lowBits >= 0) && + (lowBits < jspb.BinaryConstants.TWO_TO_32)); + goog.asserts.assert((highBits >= 0) && + (highBits < jspb.BinaryConstants.TWO_TO_32)); + + // Break the binary representation into chunks of 7 bits, set the 8th bit + // in each chunk if it's not the final chunk, and append to the result. + while (highBits > 0 || lowBits > 127) { + this.buffer_.push((lowBits & 0x7f) | 0x80); + lowBits = ((lowBits >>> 7) | (highBits << 25)) >>> 0; + highBits = highBits >>> 7; + } + this.buffer_.push(lowBits); +}; + + +/** + * Encodes a 32-bit unsigned integer into its wire-format varint representation + * and stores it in the buffer. + * @param {number} value The integer to convert. + */ +jspb.BinaryEncoder.prototype.writeUnsignedVarint32 = function(value) { + goog.asserts.assert(value == Math.floor(value)); + goog.asserts.assert((value >= 0) && + (value < jspb.BinaryConstants.TWO_TO_32)); + + while (value > 127) { + this.buffer_.push((value & 0x7f) | 0x80); + value = value >>> 7; + } + + this.buffer_.push(value); +}; + + +/** + * Encodes a 32-bit signed integer into its wire-format varint representation + * and stores it in the buffer. + * @param {number} value The integer to convert. + */ +jspb.BinaryEncoder.prototype.writeSignedVarint32 = function(value) { + goog.asserts.assert(value == Math.floor(value)); + goog.asserts.assert((value >= -jspb.BinaryConstants.TWO_TO_31) && + (value < jspb.BinaryConstants.TWO_TO_31)); + + // Use the unsigned version if the value is not negative. + if (value >= 0) { + this.writeUnsignedVarint32(value); + return; + } + + // Write nine bytes with a _signed_ right shift so we preserve the sign bit. + for (var i = 0; i < 9; i++) { + this.buffer_.push((value & 0x7f) | 0x80); + value = value >> 7; + } + + // The above loop writes out 63 bits, so the last byte is always the sign bit + // which is always set for negative numbers. + this.buffer_.push(1); +}; + + +/** + * Encodes a 64-bit unsigned integer into its wire-format varint representation + * and stores it in the buffer. Integers that are not representable in 64 bits + * will be truncated. + * @param {number} value The integer to convert. + */ +jspb.BinaryEncoder.prototype.writeUnsignedVarint64 = function(value) { + goog.asserts.assert(value == Math.floor(value)); + goog.asserts.assert((value >= 0) && + (value < jspb.BinaryConstants.TWO_TO_64)); + jspb.utils.splitInt64(value); + this.writeSplitVarint64(jspb.utils.split64Low, + jspb.utils.split64High); +}; + + +/** + * Encodes a 64-bit signed integer into its wire-format varint representation + * and stores it in the buffer. Integers that are not representable in 64 bits + * will be truncated. + * @param {number} value The integer to convert. + */ +jspb.BinaryEncoder.prototype.writeSignedVarint64 = function(value) { + goog.asserts.assert(value == Math.floor(value)); + goog.asserts.assert((value >= -jspb.BinaryConstants.TWO_TO_63) && + (value < jspb.BinaryConstants.TWO_TO_63)); + jspb.utils.splitInt64(value); + this.writeSplitVarint64(jspb.utils.split64Low, + jspb.utils.split64High); +}; + + +/** + * Encodes a JavaScript integer into its wire-format, zigzag-encoded varint + * representation and stores it in the buffer. + * @param {number} value The integer to convert. + */ +jspb.BinaryEncoder.prototype.writeZigzagVarint32 = function(value) { + goog.asserts.assert(value == Math.floor(value)); + goog.asserts.assert((value >= -jspb.BinaryConstants.TWO_TO_31) && + (value < jspb.BinaryConstants.TWO_TO_31)); + this.writeUnsignedVarint32(((value << 1) ^ (value >> 31)) >>> 0); +}; + + +/** + * Encodes a JavaScript integer into its wire-format, zigzag-encoded varint + * representation and stores it in the buffer. Integers not representable in 64 + * bits will be truncated. + * @param {number} value The integer to convert. + */ +jspb.BinaryEncoder.prototype.writeZigzagVarint64 = function(value) { + goog.asserts.assert(value == Math.floor(value)); + goog.asserts.assert((value >= -jspb.BinaryConstants.TWO_TO_63) && + (value < jspb.BinaryConstants.TWO_TO_63)); + jspb.utils.splitZigzag64(value); + this.writeSplitVarint64(jspb.utils.split64Low, + jspb.utils.split64High); +}; + + +/** + * Writes a 8-bit unsigned integer to the buffer. Numbers outside the range + * [0,2^8) will be truncated. + * @param {number} value The value to write. + */ +jspb.BinaryEncoder.prototype.writeUint8 = function(value) { + goog.asserts.assert(value == Math.floor(value)); + goog.asserts.assert((value >= 0) && (value < 256)); + this.buffer_.push((value >>> 0) & 0xFF); +}; + + +/** + * Writes a 16-bit unsigned integer to the buffer. Numbers outside the + * range [0,2^16) will be truncated. + * @param {number} value The value to write. + */ +jspb.BinaryEncoder.prototype.writeUint16 = function(value) { + goog.asserts.assert(value == Math.floor(value)); + goog.asserts.assert((value >= 0) && (value < 65536)); + this.buffer_.push((value >>> 0) & 0xFF); + this.buffer_.push((value >>> 8) & 0xFF); +}; + + +/** + * Writes a 32-bit unsigned integer to the buffer. Numbers outside the + * range [0,2^32) will be truncated. + * @param {number} value The value to write. + */ +jspb.BinaryEncoder.prototype.writeUint32 = function(value) { + goog.asserts.assert(value == Math.floor(value)); + goog.asserts.assert((value >= 0) && + (value < jspb.BinaryConstants.TWO_TO_32)); + this.buffer_.push((value >>> 0) & 0xFF); + this.buffer_.push((value >>> 8) & 0xFF); + this.buffer_.push((value >>> 16) & 0xFF); + this.buffer_.push((value >>> 24) & 0xFF); +}; + + +/** + * Writes a 64-bit unsigned integer to the buffer. Numbers outside the + * range [0,2^64) will be truncated. + * @param {number} value The value to write. + */ +jspb.BinaryEncoder.prototype.writeUint64 = function(value) { + goog.asserts.assert(value == Math.floor(value)); + goog.asserts.assert((value >= 0) && + (value < jspb.BinaryConstants.TWO_TO_64)); + jspb.utils.splitUint64(value); + this.writeUint32(jspb.utils.split64Low); + this.writeUint32(jspb.utils.split64High); +}; + + +/** + * Writes a 8-bit integer to the buffer. Numbers outside the range + * [-2^7,2^7) will be truncated. + * @param {number} value The value to write. + */ +jspb.BinaryEncoder.prototype.writeInt8 = function(value) { + goog.asserts.assert(value == Math.floor(value)); + goog.asserts.assert((value >= -128) && (value < 128)); + this.buffer_.push((value >>> 0) & 0xFF); +}; + + +/** + * Writes a 16-bit integer to the buffer. Numbers outside the range + * [-2^15,2^15) will be truncated. + * @param {number} value The value to write. + */ +jspb.BinaryEncoder.prototype.writeInt16 = function(value) { + goog.asserts.assert(value == Math.floor(value)); + goog.asserts.assert((value >= -32768) && (value < 32768)); + this.buffer_.push((value >>> 0) & 0xFF); + this.buffer_.push((value >>> 8) & 0xFF); +}; + + +/** + * Writes a 32-bit integer to the buffer. Numbers outside the range + * [-2^31,2^31) will be truncated. + * @param {number} value The value to write. + */ +jspb.BinaryEncoder.prototype.writeInt32 = function(value) { + goog.asserts.assert(value == Math.floor(value)); + goog.asserts.assert((value >= -jspb.BinaryConstants.TWO_TO_31) && + (value < jspb.BinaryConstants.TWO_TO_31)); + this.buffer_.push((value >>> 0) & 0xFF); + this.buffer_.push((value >>> 8) & 0xFF); + this.buffer_.push((value >>> 16) & 0xFF); + this.buffer_.push((value >>> 24) & 0xFF); +}; + + +/** + * Writes a 64-bit integer to the buffer. Numbers outside the range + * [-2^63,2^63) will be truncated. + * @param {number} value The value to write. + */ +jspb.BinaryEncoder.prototype.writeInt64 = function(value) { + goog.asserts.assert(value == Math.floor(value)); + goog.asserts.assert((value >= -jspb.BinaryConstants.TWO_TO_63) && + (value < jspb.BinaryConstants.TWO_TO_63)); + jspb.utils.splitInt64(value); + this.writeUint32(jspb.utils.split64Low); + this.writeUint32(jspb.utils.split64High); +}; + + +/** + * Writes a single-precision floating point value to the buffer. Numbers + * requiring more than 32 bits of precision will be truncated. + * @param {number} value The value to write. + */ +jspb.BinaryEncoder.prototype.writeFloat = function(value) { + goog.asserts.assert((value >= -jspb.BinaryConstants.FLOAT32_MAX) && + (value <= jspb.BinaryConstants.FLOAT32_MAX)); + jspb.utils.splitFloat32(value); + this.writeUint32(jspb.utils.split64Low); +}; + + +/** + * Writes a double-precision floating point value to the buffer. As this is + * the native format used by JavaScript, no precision will be lost. + * @param {number} value The value to write. + */ +jspb.BinaryEncoder.prototype.writeDouble = function(value) { + goog.asserts.assert((value >= -jspb.BinaryConstants.FLOAT64_MAX) && + (value <= jspb.BinaryConstants.FLOAT64_MAX)); + jspb.utils.splitFloat64(value); + this.writeUint32(jspb.utils.split64Low); + this.writeUint32(jspb.utils.split64High); +}; + + +/** + * Writes a boolean value to the buffer as a varint. + * @param {boolean} value The value to write. + */ +jspb.BinaryEncoder.prototype.writeBool = function(value) { + goog.asserts.assert(goog.isBoolean(value)); + this.buffer_.push(value ? 1 : 0); +}; + + +/** + * Writes an enum value to the buffer as a varint. + * @param {number} value The value to write. + */ +jspb.BinaryEncoder.prototype.writeEnum = function(value) { + goog.asserts.assert(value == Math.floor(value)); + goog.asserts.assert((value >= -jspb.BinaryConstants.TWO_TO_31) && + (value < jspb.BinaryConstants.TWO_TO_31)); + this.writeSignedVarint32(value); +}; + + +/** + * Writes an arbitrary byte array to the buffer. + * @param {!Uint8Array} bytes The array of bytes to write. + */ +jspb.BinaryEncoder.prototype.writeBytes = function(bytes) { + this.buffer_.push.apply(this.buffer_, bytes); +}; + + +/** + * Writes a 64-bit hash string (8 characters @ 8 bits of data each) to the + * buffer as a varint. + * @param {string} hash The hash to write. + */ +jspb.BinaryEncoder.prototype.writeVarintHash64 = function(hash) { + jspb.utils.splitHash64(hash); + this.writeSplitVarint64(jspb.utils.split64Low, + jspb.utils.split64High); +}; + + +/** + * Writes a 64-bit hash string (8 characters @ 8 bits of data each) to the + * buffer as a fixed64. + * @param {string} hash The hash to write. + */ +jspb.BinaryEncoder.prototype.writeFixedHash64 = function(hash) { + jspb.utils.splitHash64(hash); + this.writeUint32(jspb.utils.split64Low); + this.writeUint32(jspb.utils.split64High); +}; + + +/** + * Writes a UTF16 Javascript string to the buffer encoded as UTF8. + * TODO(aappleby): Add support for surrogate pairs, reject unpaired surrogates. + * @param {string} value The string to write. + * @return {number} The number of bytes used to encode the string. + */ +jspb.BinaryEncoder.prototype.writeString = function(value) { + var oldLength = this.buffer_.length; + + // UTF16 to UTF8 conversion loop swiped from goog.crypt.stringToUtf8ByteArray. + for (var i = 0; i < value.length; i++) { + var c = value.charCodeAt(i); + if (c < 128) { + this.buffer_.push(c); + } else if (c < 2048) { + this.buffer_.push((c >> 6) | 192); + this.buffer_.push((c & 63) | 128); + } else { + this.buffer_.push((c >> 12) | 224); + this.buffer_.push(((c >> 6) & 63) | 128); + this.buffer_.push((c & 63) | 128); + } + } + + var length = this.buffer_.length - oldLength; + return length; +}; diff --git a/js/binary/proto_test.js b/js/binary/proto_test.js index 3b4aa17f..14d0f42e 100644 --- a/js/binary/proto_test.js +++ b/js/binary/proto_test.js @@ -30,7 +30,9 @@ // Test suite is written using Jasmine -- see http://jasmine.github.io/ +goog.require('goog.crypt.base64'); goog.require('goog.testing.asserts'); +goog.require('jspb.Message'); // CommonJS-LoadFromFile: ../testbinary_pb proto.jspb.test goog.require('proto.jspb.test.ExtendsWithMessage'); @@ -38,9 +40,61 @@ goog.require('proto.jspb.test.ForeignEnum'); goog.require('proto.jspb.test.ForeignMessage'); goog.require('proto.jspb.test.TestAllTypes'); goog.require('proto.jspb.test.TestExtendable'); +goog.require('proto.jspb.test.extendOptionalBool'); +goog.require('proto.jspb.test.extendOptionalBytes'); +goog.require('proto.jspb.test.extendOptionalDouble'); +goog.require('proto.jspb.test.extendOptionalFixed32'); +goog.require('proto.jspb.test.extendOptionalFixed64'); +goog.require('proto.jspb.test.extendOptionalFloat'); +goog.require('proto.jspb.test.extendOptionalForeignEnum'); +goog.require('proto.jspb.test.extendOptionalInt32'); +goog.require('proto.jspb.test.extendOptionalInt64'); +goog.require('proto.jspb.test.extendOptionalSfixed32'); +goog.require('proto.jspb.test.extendOptionalSfixed64'); +goog.require('proto.jspb.test.extendOptionalSint32'); +goog.require('proto.jspb.test.extendOptionalSint64'); +goog.require('proto.jspb.test.extendOptionalString'); +goog.require('proto.jspb.test.extendOptionalUint32'); +goog.require('proto.jspb.test.extendOptionalUint64'); +goog.require('proto.jspb.test.extendPackedRepeatedBoolList'); +goog.require('proto.jspb.test.extendPackedRepeatedDoubleList'); +goog.require('proto.jspb.test.extendPackedRepeatedFixed32List'); +goog.require('proto.jspb.test.extendPackedRepeatedFixed64List'); +goog.require('proto.jspb.test.extendPackedRepeatedFloatList'); +goog.require('proto.jspb.test.extendPackedRepeatedForeignEnumList'); +goog.require('proto.jspb.test.extendPackedRepeatedInt32List'); +goog.require('proto.jspb.test.extendPackedRepeatedInt64List'); +goog.require('proto.jspb.test.extendPackedRepeatedSfixed32List'); +goog.require('proto.jspb.test.extendPackedRepeatedSfixed64List'); +goog.require('proto.jspb.test.extendPackedRepeatedSint32List'); +goog.require('proto.jspb.test.extendPackedRepeatedSint64List'); +goog.require('proto.jspb.test.extendPackedRepeatedUint32List'); +goog.require('proto.jspb.test.extendPackedRepeatedUint64List'); +goog.require('proto.jspb.test.extendRepeatedBoolList'); +goog.require('proto.jspb.test.extendRepeatedBytesList'); +goog.require('proto.jspb.test.extendRepeatedDoubleList'); +goog.require('proto.jspb.test.extendRepeatedFixed32List'); +goog.require('proto.jspb.test.extendRepeatedFixed64List'); +goog.require('proto.jspb.test.extendRepeatedFloatList'); +goog.require('proto.jspb.test.extendRepeatedForeignEnumList'); +goog.require('proto.jspb.test.extendRepeatedInt32List'); +goog.require('proto.jspb.test.extendRepeatedInt64List'); +goog.require('proto.jspb.test.extendRepeatedSfixed32List'); +goog.require('proto.jspb.test.extendRepeatedSfixed64List'); +goog.require('proto.jspb.test.extendRepeatedSint32List'); +goog.require('proto.jspb.test.extendRepeatedSint64List'); +goog.require('proto.jspb.test.extendRepeatedStringList'); +goog.require('proto.jspb.test.extendRepeatedUint32List'); +goog.require('proto.jspb.test.extendRepeatedUint64List'); + var suite = {}; +var BYTES = new Uint8Array([1, 2, 8, 9]); + +var BYTES_B64 = goog.crypt.base64.encodeByteArray(BYTES); + + /** * Helper: fill all fields on a TestAllTypes message. * @param {proto.jspb.test.TestAllTypes} msg @@ -62,7 +116,7 @@ function fillAllFields(msg) { msg.setOptionalDouble(-1.5); msg.setOptionalBool(true); msg.setOptionalString('hello world'); - msg.setOptionalBytes('bytes'); + msg.setOptionalBytes(BYTES); msg.setOptionalGroup(new proto.jspb.test.TestAllTypes.OptionalGroup()); msg.getOptionalGroup().setA(100); var submsg = new proto.jspb.test.ForeignMessage(); @@ -71,6 +125,7 @@ function fillAllFields(msg) { msg.setOptionalForeignEnum(proto.jspb.test.ForeignEnum.FOREIGN_FOO); msg.setOneofString('oneof'); + msg.setRepeatedInt32List([-42]); msg.setRepeatedInt64List([-0x7fffffff00000000]); msg.setRepeatedUint32List([0x80000000]); @@ -85,7 +140,7 @@ function fillAllFields(msg) { msg.setRepeatedDoubleList([-1.5]); msg.setRepeatedBoolList([true]); msg.setRepeatedStringList(['hello world']); - msg.setRepeatedBytesList(['bytes']); + msg.setRepeatedBytesList([BYTES, BYTES]); msg.setRepeatedGroupList([new proto.jspb.test.TestAllTypes.RepeatedGroup()]); msg.getRepeatedGroupList()[0].setA(100); submsg = new proto.jspb.test.ForeignMessage(); @@ -106,106 +161,115 @@ function fillAllFields(msg) { msg.setPackedRepeatedFloatList([1.5]); msg.setPackedRepeatedDoubleList([-1.5]); msg.setPackedRepeatedBoolList([true]); + } /** - * Helper: compare a bytes field to a string with codepoints 0--255. + * Helper: compare a bytes field to an expected value * @param {Uint8Array|string} arr - * @param {string} str + * @param {Uint8Array} expected * @return {boolean} */ -function bytesCompare(arr, str) { - if (arr.length != str.length) { +function bytesCompare(arr, expected) { + if (goog.isString(arr)) { + arr = goog.crypt.base64.decodeStringToUint8Array(arr); + } + if (arr.length != expected.length) { return false; } - if (typeof arr == 'string') { - for (var i = 0; i < arr.length; i++) { - if (arr.charCodeAt(i) != str.charCodeAt(i)) { - return false; - } + for (var i = 0; i < arr.length; i++) { + if (arr[i] != expected[i]) { + return false; } - return true; - } else { - for (var i = 0; i < arr.length; i++) { - if (arr[i] != str.charCodeAt(i)) { - return false; - } - } - return true; } + return true; } /** * Helper: verify contents of given TestAllTypes message as set by * fillAllFields(). - * @param {proto.jspb.test.TestAllTypes} msg + * @param {proto.jspb.test.TestAllTypes} original + * @param {proto.jspb.test.TestAllTypes} copy */ -function checkAllFields(msg) { - assertEquals(msg.getOptionalInt32(), -42); - assertEquals(msg.getOptionalInt64(), -0x7fffffff00000000); - assertEquals(msg.getOptionalUint32(), 0x80000000); - assertEquals(msg.getOptionalUint64(), 0xf000000000000000); - assertEquals(msg.getOptionalSint32(), -100); - assertEquals(msg.getOptionalSint64(), -0x8000000000000000); - assertEquals(msg.getOptionalFixed32(), 1234); - assertEquals(msg.getOptionalFixed64(), 0x1234567800000000); - assertEquals(msg.getOptionalSfixed32(), -1234); - assertEquals(msg.getOptionalSfixed64(), -0x1234567800000000); - assertEquals(msg.getOptionalFloat(), 1.5); - assertEquals(msg.getOptionalDouble(), -1.5); - assertEquals(msg.getOptionalBool(), true); - assertEquals(msg.getOptionalString(), 'hello world'); - assertEquals(true, bytesCompare(msg.getOptionalBytes(), 'bytes')); - assertEquals(msg.getOptionalGroup().getA(), 100); - assertEquals(msg.getOptionalForeignMessage().getC(), 16); - assertEquals(msg.getOptionalForeignEnum(), +function checkAllFields(original, copy) { + assertTrue(jspb.Message.equals(original, copy)); + + assertEquals(copy.getOptionalInt32(), -42); + assertEquals(copy.getOptionalInt64(), -0x7fffffff00000000); + assertEquals(copy.getOptionalUint32(), 0x80000000); + assertEquals(copy.getOptionalUint64(), 0xf000000000000000); + assertEquals(copy.getOptionalSint32(), -100); + assertEquals(copy.getOptionalSint64(), -0x8000000000000000); + assertEquals(copy.getOptionalFixed32(), 1234); + assertEquals(copy.getOptionalFixed64(), 0x1234567800000000); + assertEquals(copy.getOptionalSfixed32(), -1234); + assertEquals(copy.getOptionalSfixed64(), -0x1234567800000000); + assertEquals(copy.getOptionalFloat(), 1.5); + assertEquals(copy.getOptionalDouble(), -1.5); + assertEquals(copy.getOptionalBool(), true); + assertEquals(copy.getOptionalString(), 'hello world'); + assertEquals(true, bytesCompare(copy.getOptionalBytes(), BYTES)); + assertEquals(true, bytesCompare(copy.getOptionalBytes_asU8(), BYTES)); + assertEquals( + copy.getOptionalBytes_asB64(), goog.crypt.base64.encodeByteArray(BYTES)); + + assertEquals(copy.getOptionalGroup().getA(), 100); + assertEquals(copy.getOptionalForeignMessage().getC(), 16); + assertEquals(copy.getOptionalForeignEnum(), proto.jspb.test.ForeignEnum.FOREIGN_FOO); - assertEquals(msg.getOneofString(), 'oneof'); - assertEquals(msg.getOneofFieldCase(), + + + assertEquals(copy.getOneofString(), 'oneof'); + assertEquals(copy.getOneofFieldCase(), proto.jspb.test.TestAllTypes.OneofFieldCase.ONEOF_STRING); - assertElementsEquals(msg.getRepeatedInt32List(), [-42]); - assertElementsEquals(msg.getRepeatedInt64List(), [-0x7fffffff00000000]); - assertElementsEquals(msg.getRepeatedUint32List(), [0x80000000]); - assertElementsEquals(msg.getRepeatedUint64List(), [0xf000000000000000]); - assertElementsEquals(msg.getRepeatedSint32List(), [-100]); - assertElementsEquals(msg.getRepeatedSint64List(), [-0x8000000000000000]); - assertElementsEquals(msg.getRepeatedFixed32List(), [1234]); - assertElementsEquals(msg.getRepeatedFixed64List(), [0x1234567800000000]); - assertElementsEquals(msg.getRepeatedSfixed32List(), [-1234]); - assertElementsEquals(msg.getRepeatedSfixed64List(), [-0x1234567800000000]); - assertElementsEquals(msg.getRepeatedFloatList(), [1.5]); - assertElementsEquals(msg.getRepeatedDoubleList(), [-1.5]); - assertElementsEquals(msg.getRepeatedBoolList(), [true]); - assertElementsEquals(msg.getRepeatedStringList(), ['hello world']); - assertEquals(msg.getRepeatedBytesList().length, 1); - assertEquals(true, bytesCompare(msg.getRepeatedBytesList()[0], 'bytes')); - assertEquals(msg.getRepeatedGroupList().length, 1); - assertEquals(msg.getRepeatedGroupList()[0].getA(), 100); - assertEquals(msg.getRepeatedForeignMessageList().length, 1); - assertEquals(msg.getRepeatedForeignMessageList()[0].getC(), 1000); - assertElementsEquals(msg.getRepeatedForeignEnumList(), + assertElementsEquals(copy.getRepeatedInt32List(), [-42]); + assertElementsEquals(copy.getRepeatedInt64List(), [-0x7fffffff00000000]); + assertElementsEquals(copy.getRepeatedUint32List(), [0x80000000]); + assertElementsEquals(copy.getRepeatedUint64List(), [0xf000000000000000]); + assertElementsEquals(copy.getRepeatedSint32List(), [-100]); + assertElementsEquals(copy.getRepeatedSint64List(), [-0x8000000000000000]); + assertElementsEquals(copy.getRepeatedFixed32List(), [1234]); + assertElementsEquals(copy.getRepeatedFixed64List(), [0x1234567800000000]); + assertElementsEquals(copy.getRepeatedSfixed32List(), [-1234]); + assertElementsEquals(copy.getRepeatedSfixed64List(), [-0x1234567800000000]); + assertElementsEquals(copy.getRepeatedFloatList(), [1.5]); + assertElementsEquals(copy.getRepeatedDoubleList(), [-1.5]); + assertElementsEquals(copy.getRepeatedBoolList(), [true]); + assertElementsEquals(copy.getRepeatedStringList(), ['hello world']); + assertEquals(copy.getRepeatedBytesList().length, 2); + assertEquals(true, bytesCompare(copy.getRepeatedBytesList_asU8()[0], BYTES)); + assertEquals(true, bytesCompare(copy.getRepeatedBytesList()[0], BYTES)); + assertEquals(true, bytesCompare(copy.getRepeatedBytesList_asU8()[1], BYTES)); + assertEquals(copy.getRepeatedBytesList_asB64()[0], BYTES_B64); + assertEquals(copy.getRepeatedBytesList_asB64()[1], BYTES_B64); + assertEquals(copy.getRepeatedGroupList().length, 1); + assertEquals(copy.getRepeatedGroupList()[0].getA(), 100); + assertEquals(copy.getRepeatedForeignMessageList().length, 1); + assertEquals(copy.getRepeatedForeignMessageList()[0].getC(), 1000); + assertElementsEquals(copy.getRepeatedForeignEnumList(), [proto.jspb.test.ForeignEnum.FOREIGN_FOO]); - assertElementsEquals(msg.getPackedRepeatedInt32List(), [-42]); - assertElementsEquals(msg.getPackedRepeatedInt64List(), + assertElementsEquals(copy.getPackedRepeatedInt32List(), [-42]); + assertElementsEquals(copy.getPackedRepeatedInt64List(), [-0x7fffffff00000000]); - assertElementsEquals(msg.getPackedRepeatedUint32List(), [0x80000000]); - assertElementsEquals(msg.getPackedRepeatedUint64List(), [0xf000000000000000]); - assertElementsEquals(msg.getPackedRepeatedSint32List(), [-100]); - assertElementsEquals(msg.getPackedRepeatedSint64List(), + assertElementsEquals(copy.getPackedRepeatedUint32List(), [0x80000000]); + assertElementsEquals(copy.getPackedRepeatedUint64List(), + [0xf000000000000000]); + assertElementsEquals(copy.getPackedRepeatedSint32List(), [-100]); + assertElementsEquals(copy.getPackedRepeatedSint64List(), [-0x8000000000000000]); - assertElementsEquals(msg.getPackedRepeatedFixed32List(), [1234]); - assertElementsEquals(msg.getPackedRepeatedFixed64List(), + assertElementsEquals(copy.getPackedRepeatedFixed32List(), [1234]); + assertElementsEquals(copy.getPackedRepeatedFixed64List(), [0x1234567800000000]); - assertElementsEquals(msg.getPackedRepeatedSfixed32List(), [-1234]); - assertElementsEquals(msg.getPackedRepeatedSfixed64List(), + assertElementsEquals(copy.getPackedRepeatedSfixed32List(), [-1234]); + assertElementsEquals(copy.getPackedRepeatedSfixed64List(), [-0x1234567800000000]); - assertElementsEquals(msg.getPackedRepeatedFloatList(), [1.5]); - assertElementsEquals(msg.getPackedRepeatedDoubleList(), [-1.5]); - assertElementsEquals(msg.getPackedRepeatedBoolList(), [true]); + assertElementsEquals(copy.getPackedRepeatedFloatList(), [1.5]); + assertElementsEquals(copy.getPackedRepeatedDoubleList(), [-1.5]); + } @@ -242,14 +306,13 @@ function checkExtensions(msg) { msg.getExtension(proto.jspb.test.extendOptionalBool)); assertEquals('hello world', msg.getExtension(proto.jspb.test.extendOptionalString)); - assertEquals(true, - bytesCompare(msg.getExtension(proto.jspb.test.extendOptionalBytes), - 'bytes')); + assertEquals( + true, bytesCompare( + msg.getExtension(proto.jspb.test.extendOptionalBytes), BYTES)); assertEquals(16, msg.getExtension( proto.jspb.test.ExtendsWithMessage.optionalExtension).getFoo()); - assertEquals(proto.jspb.test.ForeignEnum.FOREIGN_FOO, - msg.getExtension(proto.jspb.test.extendOptionalForeignEnum)); + assertElementsEquals( msg.getExtension(proto.jspb.test.extendRepeatedInt32List), @@ -293,10 +356,10 @@ function checkExtensions(msg) { assertElementsEquals( msg.getExtension(proto.jspb.test.extendRepeatedStringList), ['hello world']); - assertEquals(true, + assertEquals( + true, bytesCompare( - msg.getExtension(proto.jspb.test.extendRepeatedBytesList)[0], - 'bytes')); + msg.getExtension(proto.jspb.test.extendRepeatedBytesList)[0], BYTES)); assertEquals(1000, msg.getExtension( proto.jspb.test.ExtendsWithMessage.repeatedExtensionList)[0] @@ -305,6 +368,7 @@ function checkExtensions(msg) { msg.getExtension(proto.jspb.test.extendRepeatedForeignEnumList), [proto.jspb.test.ForeignEnum.FOREIGN_FOO]); + assertElementsEquals( msg.getExtension(proto.jspb.test.extendPackedRepeatedInt32List), [-42]); @@ -347,6 +411,7 @@ function checkExtensions(msg) { assertElementsEquals( msg.getExtension(proto.jspb.test.extendPackedRepeatedForeignEnumList), [proto.jspb.test.ForeignEnum.FOREIGN_FOO]); + } @@ -360,9 +425,82 @@ describe('protoBinaryTest', function() { fillAllFields(msg); var encoded = msg.serializeBinary(); var decoded = proto.jspb.test.TestAllTypes.deserializeBinary(encoded); - checkAllFields(decoded); + checkAllFields(msg, decoded); }); + /** + * Test that base64 string and Uint8Array are interchangeable in bytes fields. + */ + it('testBytesFieldsGettersInterop', function() { + var msg = new proto.jspb.test.TestAllTypes(); + // Set from a base64 string and check all the getters work. + msg.setOptionalBytes(BYTES_B64); + assertTrue(bytesCompare(msg.getOptionalBytes_asU8(), BYTES)); + assertTrue(bytesCompare(msg.getOptionalBytes_asB64(), BYTES)); + assertTrue(bytesCompare(msg.getOptionalBytes(), BYTES)); + + // Test binary serialize round trip doesn't break it. + msg = proto.jspb.test.TestAllTypes.deserializeBinary(msg.serializeBinary()); + assertTrue(bytesCompare(msg.getOptionalBytes_asU8(), BYTES)); + assertTrue(bytesCompare(msg.getOptionalBytes_asB64(), BYTES)); + assertTrue(bytesCompare(msg.getOptionalBytes(), BYTES)); + + msg = new proto.jspb.test.TestAllTypes(); + // Set from a Uint8Array and check all the getters work. + msg.setOptionalBytes(BYTES); + assertTrue(bytesCompare(msg.getOptionalBytes_asU8(), BYTES)); + assertTrue(bytesCompare(msg.getOptionalBytes_asB64(), BYTES)); + assertTrue(bytesCompare(msg.getOptionalBytes(), BYTES)); + + }); + + /** + * Test that bytes setters will receive result of any of the getters. + */ + it('testBytesFieldsSettersInterop', function() { + var msg = new proto.jspb.test.TestAllTypes(); + msg.setOptionalBytes(BYTES); + assertTrue(bytesCompare(msg.getOptionalBytes(), BYTES)); + + msg.setOptionalBytes(msg.getOptionalBytes()); + assertTrue(bytesCompare(msg.getOptionalBytes(), BYTES)); + msg.setOptionalBytes(msg.getOptionalBytes_asB64()); + assertTrue(bytesCompare(msg.getOptionalBytes(), BYTES)); + msg.setOptionalBytes(msg.getOptionalBytes_asU8()); + assertTrue(bytesCompare(msg.getOptionalBytes(), BYTES)); + }); + + /** + * Test that bytes setters will receive result of any of the getters. + */ + it('testRepeatedBytesGetters', function() { + var msg = new proto.jspb.test.TestAllTypes(); + + function assertGetters() { + assertTrue(goog.isString(msg.getRepeatedBytesList_asB64()[0])); + assertTrue(goog.isString(msg.getRepeatedBytesList_asB64()[1])); + assertTrue(msg.getRepeatedBytesList_asU8()[0] instanceof Uint8Array); + assertTrue(msg.getRepeatedBytesList_asU8()[1] instanceof Uint8Array); + + assertTrue(bytesCompare(msg.getRepeatedBytesList()[0], BYTES)); + assertTrue(bytesCompare(msg.getRepeatedBytesList()[1], BYTES)); + assertTrue(bytesCompare(msg.getRepeatedBytesList_asB64()[0], BYTES)); + assertTrue(bytesCompare(msg.getRepeatedBytesList_asB64()[1], BYTES)); + assertTrue(bytesCompare(msg.getRepeatedBytesList_asU8()[0], BYTES)); + assertTrue(bytesCompare(msg.getRepeatedBytesList_asU8()[1], BYTES)); + } + + msg.setRepeatedBytesList([BYTES, BYTES]); + assertGetters(); + + msg.setRepeatedBytesList([BYTES_B64, BYTES_B64]); + assertGetters(); + + msg.setRepeatedBytesList(null); + assertEquals(0, msg.getRepeatedBytesList().length); + assertEquals(0, msg.getRepeatedBytesList_asB64().length); + assertEquals(0, msg.getRepeatedBytesList_asU8().length); + }); /** * Helper: fill all extension values. @@ -397,8 +535,7 @@ describe('protoBinaryTest', function() { proto.jspb.test.extendOptionalBool, true); msg.setExtension( proto.jspb.test.extendOptionalString, 'hello world'); - msg.setExtension( - proto.jspb.test.extendOptionalBytes, 'bytes'); + msg.setExtension(proto.jspb.test.extendOptionalBytes, BYTES); var submsg = new proto.jspb.test.ExtendsWithMessage(); submsg.setFoo(16); msg.setExtension( @@ -407,6 +544,7 @@ describe('protoBinaryTest', function() { proto.jspb.test.extendOptionalForeignEnum, proto.jspb.test.ForeignEnum.FOREIGN_FOO); + msg.setExtension( proto.jspb.test.extendRepeatedInt32List, [-42]); msg.setExtension( @@ -435,8 +573,7 @@ describe('protoBinaryTest', function() { proto.jspb.test.extendRepeatedBoolList, [true]); msg.setExtension( proto.jspb.test.extendRepeatedStringList, ['hello world']); - msg.setExtension( - proto.jspb.test.extendRepeatedBytesList, ['bytes']); + msg.setExtension(proto.jspb.test.extendRepeatedBytesList, [BYTES]); submsg = new proto.jspb.test.ExtendsWithMessage(); submsg.setFoo(1000); msg.setExtension( @@ -444,6 +581,7 @@ describe('protoBinaryTest', function() { msg.setExtension(proto.jspb.test.extendRepeatedForeignEnumList, [proto.jspb.test.ForeignEnum.FOREIGN_FOO]); + msg.setExtension( proto.jspb.test.extendPackedRepeatedInt32List, [-42]); msg.setExtension( @@ -473,6 +611,7 @@ describe('protoBinaryTest', function() { proto.jspb.test.extendPackedRepeatedBoolList, [true]); msg.setExtension(proto.jspb.test.extendPackedRepeatedForeignEnumList, [proto.jspb.test.ForeignEnum.FOREIGN_FOO]); + } diff --git a/js/binary/reader.js b/js/binary/reader.js index abcd1660..15f90432 100644 --- a/js/binary/reader.js +++ b/js/binary/reader.js @@ -180,7 +180,7 @@ jspb.BinaryReader.prototype.getCursor = function() { /** * Returns the raw buffer. - * @return {Uint8Array} The raw buffer. + * @return {?Uint8Array} The raw buffer. */ jspb.BinaryReader.prototype.getBuffer = function() { return this.decoder_.getBuffer(); @@ -592,8 +592,8 @@ jspb.BinaryReader.prototype.getFieldDecoder = function() { var start = this.decoder_.getCursor(); var end = start + length; - var innerDecoder = jspb.BinaryDecoder.alloc(this.decoder_.getBuffer(), - start, length); + var innerDecoder = + jspb.BinaryDecoder.alloc(this.decoder_.getBuffer(), start, length); this.decoder_.setCursor(end); return innerDecoder; }; @@ -869,7 +869,7 @@ jspb.BinaryReader.prototype.readString = function() { * Reads a length-prefixed block of bytes from the binary stream, or returns * null if the next field in the stream has an invalid length value. * - * @return {Uint8Array} The block of bytes. + * @return {!Uint8Array} The block of bytes. */ jspb.BinaryReader.prototype.readBytes = function() { goog.asserts.assert( diff --git a/js/binary/reader_test.js b/js/binary/reader_test.js index a6482610..6f7e5d45 100644 --- a/js/binary/reader_test.js +++ b/js/binary/reader_test.js @@ -366,28 +366,28 @@ describe('binaryReaderTest', function() { var writer = new jspb.BinaryWriter(); var testSignedData = [ - '2730538252207801776', - '-2688470994844604560', - '3398529779486536359', - '3568577411627971000', - '272477188847484900', - '-6649058714086158188', - '-7695254765712060806', - '-4525541438037104029', - '-4993706538836508568', - '4990160321893729138' + '2730538252207801776', + '-2688470994844604560', + '3398529779486536359', + '3568577411627971000', + '272477188847484900', + '-6649058714086158188', + '-7695254765712060806', + '-4525541438037104029', + '-4993706538836508568', + '4990160321893729138' ]; var testUnsignedData = [ - '7822732630241694882', - '6753602971916687352', - '2399935075244442116', - '8724292567325338867', - '16948784802625696584', - '4136275908516066934', - '3575388346793700364', - '5167142028379259461', - '1557573948689737699', - '17100725280812548567' + '7822732630241694882', + '6753602971916687352', + '2399935075244442116', + '8724292567325338867', + '16948784802625696584', + '4136275908516066934', + '3575388346793700364', + '5167142028379259461', + '1557573948689737699', + '17100725280812548567' ]; for (var i = 0; i < testSignedData.length; i++) { @@ -535,7 +535,7 @@ describe('binaryReaderTest', function() { */ it('testNesting', function() { var writer = new jspb.BinaryWriter(); - var dummyMessage = /** @type {!jspb.BinaryMessage} */({}); + var dummyMessage = /** @type {!jspb.BinaryMessage} */({}); writer.writeInt32(1, 100); @@ -626,31 +626,15 @@ describe('binaryReaderTest', function() { writer.writeBytes(4, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); writer.writeString(4, 'The quick brown fox jumps over the lazy dog'); - // Write a group with a nested group inside. We use the internal - // .rawWriteVarint() to ensure the tested wire data is what we want, - // independently of any serialization logic. + // Write a group with a nested group inside. writer.writeInt32(5, sentinel); - // Start group, field 5. - writer.rawWriteVarint( - (5 << 3) + jspb.BinaryConstants.WireType.START_GROUP); - // Varint, field 42. - writer.rawWriteVarint( - (42 << 3) + jspb.BinaryConstants.WireType.VARINT); - // Varint data. - writer.rawWriteVarint(42); - // Start group, field 6. - writer.rawWriteVarint( - (6 << 3) + jspb.BinaryConstants.WireType.START_GROUP); - // Varint, field 84. - writer.rawWriteVarint( - (84 << 3) + jspb.BinaryConstants.WireType.VARINT); - writer.rawWriteVarint(42); - // End group, field 6. - writer.rawWriteVarint( - (6 << 3) + jspb.BinaryConstants.WireType.END_GROUP); - // End group, field 5. - writer.rawWriteVarint( - (5 << 3) + jspb.BinaryConstants.WireType.END_GROUP); + var dummyMessage = /** @type {!jspb.BinaryMessage} */({}); + writer.writeGroup(5, dummyMessage, function() { + writer.writeInt64(42, 42); + writer.writeGroup(6, dummyMessage, function() { + writer.writeInt64(84, 42); + }); + }); // Write final sentinel. writer.writeInt32(6, sentinel); diff --git a/js/binary/utils.js b/js/binary/utils.js index 92600389..875ff955 100644 --- a/js/binary/utils.js +++ b/js/binary/utils.js @@ -839,62 +839,16 @@ jspb.utils.countDelimitedFields = function(buffer, start, end, field) { /** - * Clones a scalar field. Pulling this out to a helper method saves us a few - * bytes of generated code. - * @param {Array} array - * @return {Array} - */ -jspb.utils.cloneRepeatedScalarField = function(array) { - return array ? array.slice() : null; -}; - - -/** - * Clones an array of messages using the provided cloner function. - * @param {Array.<jspb.BinaryMessage>} messages - * @param {jspb.ClonerFunction} cloner - * @return {Array.<jspb.BinaryMessage>} - */ -jspb.utils.cloneRepeatedMessageField = function(messages, cloner) { - if (messages === null) return null; - var result = []; - for (var i = 0; i < messages.length; i++) { - result.push(cloner(messages[i])); - } - return result; -}; - - -/** - * Clones an array of byte blobs. - * @param {Array.<Uint8Array>} blobs - * @return {Array.<Uint8Array>} - */ -jspb.utils.cloneRepeatedBlobField = function(blobs) { - if (blobs === null) return null; - var result = []; - for (var i = 0; i < blobs.length; i++) { - result.push(new Uint8Array(blobs[i])); - } - return result; -}; - - -/** * String-ify bytes for text format. Should be optimized away in non-debug. * The returned string uses \xXX escapes for all values and is itself quoted. * [1, 31] serializes to '"\x01\x1f"'. * @param {jspb.ByteSource} byteSource The bytes to serialize. - * @param {boolean=} opt_stringIsRawBytes The string is interpreted as a series - * of raw bytes rather than base64 data. * @return {string} Stringified bytes for text format. */ -jspb.utils.debugBytesToTextFormat = function(byteSource, - opt_stringIsRawBytes) { +jspb.utils.debugBytesToTextFormat = function(byteSource) { var s = '"'; if (byteSource) { - var bytes = - jspb.utils.byteSourceToUint8Array(byteSource, opt_stringIsRawBytes); + var bytes = jspb.utils.byteSourceToUint8Array(byteSource); for (var i = 0; i < bytes.length; i++) { s += '\\x'; if (bytes[i] < 16) s += '0'; @@ -925,9 +879,8 @@ jspb.utils.debugScalarToTextFormat = function(scalar) { * exception. * @param {string} str * @return {!Uint8Array} - * @private */ -jspb.utils.stringToByteArray_ = function(str) { +jspb.utils.stringToByteArray = function(str) { var arr = new Uint8Array(str.length); for (var i = 0; i < str.length; i++) { var codepoint = str.charCodeAt(i); @@ -944,13 +897,10 @@ jspb.utils.stringToByteArray_ = function(str) { /** * Converts any type defined in jspb.ByteSource into a Uint8Array. * @param {!jspb.ByteSource} data - * @param {boolean=} opt_stringIsRawBytes Interpret a string as a series of raw - * bytes (encoded as codepoints 0--255 inclusive) rather than base64 data - * (default behavior). * @return {!Uint8Array} * @suppress {invalidCasts} */ -jspb.utils.byteSourceToUint8Array = function(data, opt_stringIsRawBytes) { +jspb.utils.byteSourceToUint8Array = function(data) { if (data.constructor === Uint8Array) { return /** @type {!Uint8Array} */(data); } @@ -967,11 +917,7 @@ jspb.utils.byteSourceToUint8Array = function(data, opt_stringIsRawBytes) { if (data.constructor === String) { data = /** @type {string} */(data); - if (opt_stringIsRawBytes) { - return jspb.utils.stringToByteArray_(data); - } else { - return goog.crypt.base64.decodeStringToUint8Array(data); - } + return goog.crypt.base64.decodeStringToUint8Array(data); } goog.asserts.fail('Type not convertible to Uint8Array.'); diff --git a/js/binary/utils_test.js b/js/binary/utils_test.js index 5c330791..518d7597 100644 --- a/js/binary/utils_test.js +++ b/js/binary/utils_test.js @@ -310,7 +310,7 @@ describe('binaryUtilsTest', function() { // NaN. jspb.utils.splitFloat32(NaN); if (!isNaN(jspb.utils.joinFloat32(jspb.utils.split64Low, - jspb.utils.split64High))) { + jspb.utils.split64High))) { throw 'fail!'; } @@ -324,7 +324,7 @@ describe('binaryUtilsTest', function() { if (opt_bits != jspb.utils.split64Low) throw 'fail!'; } if (truncate(x) != jspb.utils.joinFloat32(jspb.utils.split64Low, - jspb.utils.split64High)) { + jspb.utils.split64High)) { throw 'fail!'; } } @@ -376,7 +376,7 @@ describe('binaryUtilsTest', function() { // NaN. jspb.utils.splitFloat64(NaN); if (!isNaN(jspb.utils.joinFloat64(jspb.utils.split64Low, - jspb.utils.split64High))) { + jspb.utils.split64High))) { throw 'fail!'; } @@ -394,7 +394,7 @@ describe('binaryUtilsTest', function() { if (opt_lowBits != jspb.utils.split64Low) throw 'fail!'; } if (x != jspb.utils.joinFloat64(jspb.utils.split64Low, - jspb.utils.split64High)) { + jspb.utils.split64High)) { throw 'fail!'; } } @@ -439,16 +439,20 @@ describe('binaryUtilsTest', function() { * Tests counting packed varints. */ it('testCountVarints', function() { - var writer = new jspb.BinaryWriter(); - - var count = 0; + var values = []; for (var i = 1; i < 1000000000; i *= 1.1) { - writer.rawWriteVarint(Math.floor(i)); - count++; + values.push(Math.floor(i)); } + var writer = new jspb.BinaryWriter(); + writer.writePackedUint64(1, values); + var buffer = new Uint8Array(writer.getResultBuffer()); - assertEquals(count, jspb.utils.countVarints(buffer, 0, buffer.length)); + + // We should have two more varints than we started with - one for the field + // tag, one for the packed length. + assertEquals(values.length + 2, + jspb.utils.countVarints(buffer, 0, buffer.length)); }); @@ -625,8 +629,5 @@ describe('binaryUtilsTest', function() { // Converting base64-encoded strings into Uint8Arrays should work. check(convert(sourceBase64)); - - // Converting binary-data strings into Uint8Arrays should work. - check(convert(sourceString, /* opt_stringIsRawBytes = */ true)); }); }); diff --git a/js/binary/writer.js b/js/binary/writer.js index a1849457..be4478ee 100644 --- a/js/binary/writer.js +++ b/js/binary/writer.js @@ -60,12 +60,11 @@ goog.provide('jspb.BinaryWriter'); goog.require('goog.asserts'); goog.require('goog.crypt.base64'); goog.require('jspb.BinaryConstants'); +goog.require('jspb.BinaryEncoder'); goog.require('jspb.arith.Int64'); goog.require('jspb.arith.UInt64'); goog.require('jspb.utils'); -goog.forwardDeclare('jspb.Message'); - /** @@ -84,116 +83,109 @@ jspb.BinaryWriter = function() { this.blocks_ = []; /** - * Total number of bytes in the blocks_ array. Does _not_ include the temp - * buffer. + * Total number of bytes in the blocks_ array. Does _not_ include bytes in + * the encoder below. * @private {number} */ this.totalLength_ = 0; /** - * Temporary buffer holding a message that we're still serializing. When we - * get to a stopping point (either the start of a new submessage, or when we - * need to append a raw Uint8Array), the temp buffer will be added to the - * block array above and a new temp buffer will be created. - * @private {!Array.<number>} + * Binary encoder holding pieces of a message that we're still serializing. + * When we get to a stopping point (either the start of a new submessage, or + * when we need to append a raw Uint8Array), the encoder's buffer will be + * added to the block array above and the encoder will be reset. + * @private {!jspb.BinaryEncoder} */ - this.temp_ = []; + this.encoder_ = new jspb.BinaryEncoder(); /** * A stack of bookmarks containing the parent blocks for each message started * via beginSubMessage(), needed as bookkeeping for endSubMessage(). * TODO(aappleby): Deprecated, users should be calling writeMessage(). - * @private {!Array.<!jspb.BinaryWriter.Bookmark_>} + * @private {!Array.<!Array.<number>>} */ this.bookmarks_ = []; }; /** - * @typedef {{block: !Array.<number>, length: number}} - * @private - */ -jspb.BinaryWriter.Bookmark_; - - -/** - * Saves the current temp buffer in the blocks_ array and starts a new one. - * @return {!Array.<number>} Returns a reference to the old temp buffer. - * @private - */ -jspb.BinaryWriter.prototype.saveTempBuffer_ = function() { - var oldTemp = this.temp_; - this.blocks_.push(this.temp_); - this.totalLength_ += this.temp_.length; - this.temp_ = []; - return oldTemp; -}; - - -/** * Append a typed array of bytes onto the buffer. * * @param {!Uint8Array} arr The byte array to append. * @private */ jspb.BinaryWriter.prototype.appendUint8Array_ = function(arr) { - if (this.temp_.length) { - this.saveTempBuffer_(); - } + var temp = this.encoder_.end(); + this.blocks_.push(temp); this.blocks_.push(arr); - this.totalLength_ += arr.length; + this.totalLength_ += temp.length + arr.length; }; /** - * Append an untyped array of bytes onto the buffer. - * - * @param {!Array.<number>} arr The byte array to append. - * @private - */ -jspb.BinaryWriter.prototype.appendArray_ = function(arr) { - if (this.temp_.length) { - this.saveTempBuffer_(); - } - this.temp_ = arr; -}; - - -/** - * Begins a length-delimited section by writing the field header to the current - * temp buffer and then saving it in the block array. Returns the saved block, - * which we will append the length to in endDelimited_ below. + * Begins a new message by writing the field header and returning a bookmark + * which we will use to patch in the message length to in endDelimited_ below. * @param {number} field - * @return {!jspb.BinaryWriter.Bookmark_} + * @return {!Array.<number>} * @private */ jspb.BinaryWriter.prototype.beginDelimited_ = function(field) { - this.rawWriteFieldHeader_(field, jspb.BinaryConstants.WireType.DELIMITED); - return {block: this.saveTempBuffer_(), length: this.totalLength_}; + this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.DELIMITED); + var bookmark = this.encoder_.end(); + this.blocks_.push(bookmark); + this.totalLength_ += bookmark.length; + bookmark.push(this.totalLength_); + return bookmark; }; /** - * Ends a length-delimited block by encoding the _change_ in length of the - * buffer to the parent block and adds the number of bytes needed to encode - * that length to the total byte length. Note that 'parentLength' _must_ be the - * total length _after_ the field header was written in beginDelimited_ above. - * @param {!jspb.BinaryWriter.Bookmark_} bookmark + * Ends a message by encoding the _change_ in length of the buffer to the + * parent block and adds the number of bytes needed to encode that length to + * the total byte length. + * @param {!Array.<number>} bookmark * @private */ jspb.BinaryWriter.prototype.endDelimited_ = function(bookmark) { - var messageLength = this.totalLength_ + this.temp_.length - bookmark.length; + var oldLength = bookmark.pop(); + var messageLength = this.totalLength_ + this.encoder_.length() - oldLength; goog.asserts.assert(messageLength >= 0); - var bytes = 1; while (messageLength > 127) { - bookmark.block.push((messageLength & 0x7f) | 0x80); + bookmark.push((messageLength & 0x7f) | 0x80); messageLength = messageLength >>> 7; - bytes++; + this.totalLength_++; } - bookmark.block.push(messageLength); - this.totalLength_ += bytes; + bookmark.push(messageLength); + this.totalLength_++; +}; + + +/** + * Writes a pre-serialized message to the buffer. + * @param {!Uint8Array} bytes The array of bytes to write. + * @param {number} start The start of the range to write. + * @param {number} end The end of the range to write. + */ +jspb.BinaryWriter.prototype.writeSerializedMessage = function( + bytes, start, end) { + this.appendUint8Array_(bytes.subarray(start, end)); +}; + + +/** + * Writes a pre-serialized message to the buffer if the message and endpoints + * are non-null. + * @param {?Uint8Array} bytes The array of bytes to write. + * @param {?number} start The start of the range to write. + * @param {?number} end The end of the range to write. + */ +jspb.BinaryWriter.prototype.maybeWriteSerializedMessage = function( + bytes, start, end) { + if (bytes != null && start != null && end != null) { + this.writeSerializedMessage(bytes, start, end); + } }; @@ -202,7 +194,7 @@ jspb.BinaryWriter.prototype.endDelimited_ = function(bookmark) { */ jspb.BinaryWriter.prototype.reset = function() { this.blocks_ = []; - this.temp_ = []; + this.encoder_.end(); this.totalLength_ = 0; this.bookmarks_ = []; }; @@ -215,7 +207,7 @@ jspb.BinaryWriter.prototype.reset = function() { jspb.BinaryWriter.prototype.getResultBuffer = function() { goog.asserts.assert(this.bookmarks_.length == 0); - var flat = new Uint8Array(this.totalLength_ + this.temp_.length); + var flat = new Uint8Array(this.totalLength_ + this.encoder_.length()); var blocks = this.blocks_; var blockCount = blocks.length; @@ -227,8 +219,9 @@ jspb.BinaryWriter.prototype.getResultBuffer = function() { offset += block.length; } - flat.set(this.temp_, offset); - offset += this.temp_.length; + var tail = this.encoder_.end(); + flat.set(tail, offset); + offset += tail.length; // Post condition: `flattened` must have had every byte written. goog.asserts.assert(offset == flat.length); @@ -236,7 +229,6 @@ jspb.BinaryWriter.prototype.getResultBuffer = function() { // Replace our block list with the flattened block, which lets GC reclaim // the temp blocks sooner. this.blocks_ = [flat]; - this.temp_ = []; return flat; }; @@ -273,331 +265,6 @@ jspb.BinaryWriter.prototype.endSubMessage = function() { /** - * Encodes a 32-bit unsigned integer into its wire-format varint representation - * and stores it in the buffer. - * @param {number} value The integer to convert. - */ -jspb.BinaryWriter.prototype.rawWriteUnsignedVarint32 = function(value) { - goog.asserts.assert(value == Math.floor(value)); - - while (value > 127) { - this.temp_.push((value & 0x7f) | 0x80); - value = value >>> 7; - } - - this.temp_.push(value); -}; - - -/** - * Encodes a 32-bit signed integer into its wire-format varint representation - * and stores it in the buffer. - * @param {number} value The integer to convert. - */ -jspb.BinaryWriter.prototype.rawWriteSignedVarint32 = function(value) { - goog.asserts.assert(value == Math.floor(value)); - if (value >= 0) { - this.rawWriteUnsignedVarint32(value); - return; - } - - // Write nine bytes with a _signed_ right shift so we preserve the sign bit. - for (var i = 0; i < 9; i++) { - this.temp_.push((value & 0x7f) | 0x80); - value = value >> 7; - } - - // The above loop writes out 63 bits, so the last byte is always the sign bit - // which is always set for negative numbers. - this.temp_.push(1); -}; - - -/** - * Encodes an unsigned 64-bit integer in 32:32 split representation into its - * wire-format varint representation and stores it in the buffer. - * @param {number} lowBits The low 32 bits of the int. - * @param {number} highBits The high 32 bits of the int. - */ -jspb.BinaryWriter.prototype.rawWriteSplitVarint = - function(lowBits, highBits) { - // Break the binary representation into chunks of 7 bits, set the 8th bit - // in each chunk if it's not the final chunk, and append to the result. - while (highBits > 0 || lowBits > 127) { - this.temp_.push((lowBits & 0x7f) | 0x80); - lowBits = ((lowBits >>> 7) | (highBits << 25)) >>> 0; - highBits = highBits >>> 7; - } - this.temp_.push(lowBits); -}; - - -/** - * Encodes a JavaScript integer into its wire-format varint representation and - * stores it in the buffer. Due to the way the varint encoding works this - * behaves correctly for both signed and unsigned integers, though integers - * that are not representable in 64 bits will still be truncated. - * @param {number} value The integer to convert. - */ -jspb.BinaryWriter.prototype.rawWriteVarint = function(value) { - goog.asserts.assert(value == Math.floor(value)); - jspb.utils.splitInt64(value); - this.rawWriteSplitVarint(jspb.utils.split64Low, - jspb.utils.split64High); -}; - - -/** - * Encodes a jspb.arith.{Int64,UInt64} instance into its wire-format - * varint representation and stores it in the buffer. Due to the way the varint - * encoding works this behaves correctly for both signed and unsigned integers, - * though integers that are not representable in 64 bits will still be - * truncated. - * @param {jspb.arith.Int64|jspb.arith.UInt64} value - */ -jspb.BinaryWriter.prototype.rawWriteVarintFromNum = function(value) { - this.rawWriteSplitVarint(value.lo, value.hi); -}; - - -/** - * Encodes a JavaScript integer into its wire-format, zigzag-encoded varint - * representation and stores it in the buffer. - * @param {number} value The integer to convert. - */ -jspb.BinaryWriter.prototype.rawWriteZigzagVarint32 = function(value) { - goog.asserts.assert(value == Math.floor(value)); - this.rawWriteUnsignedVarint32(((value << 1) ^ (value >> 31)) >>> 0); -}; - - -/** - * Encodes a JavaScript integer into its wire-format, zigzag-encoded varint - * representation and stores it in the buffer. Integers not representable in 64 - * bits will be truncated. - * @param {number} value The integer to convert. - */ -jspb.BinaryWriter.prototype.rawWriteZigzagVarint = function(value) { - goog.asserts.assert(value == Math.floor(value)); - jspb.utils.splitZigzag64(value); - this.rawWriteSplitVarint(jspb.utils.split64Low, - jspb.utils.split64High); -}; - - -/** - * Writes a raw 8-bit unsigned integer to the buffer. Numbers outside the range - * [0,2^8) will be truncated. - * @param {number} value The value to write. - */ -jspb.BinaryWriter.prototype.rawWriteUint8 = function(value) { - goog.asserts.assert(value == Math.floor(value)); - goog.asserts.assert((value >= 0) && (value < 256)); - this.temp_.push((value >>> 0) & 0xFF); -}; - - -/** - * Writes a raw 16-bit unsigned integer to the buffer. Numbers outside the - * range [0,2^16) will be truncated. - * @param {number} value The value to write. - */ -jspb.BinaryWriter.prototype.rawWriteUint16 = function(value) { - goog.asserts.assert(value == Math.floor(value)); - goog.asserts.assert((value >= 0) && (value < 65536)); - this.temp_.push((value >>> 0) & 0xFF); - this.temp_.push((value >>> 8) & 0xFF); -}; - - -/** - * Writes a raw 32-bit unsigned integer to the buffer. Numbers outside the - * range [0,2^32) will be truncated. - * @param {number} value The value to write. - */ -jspb.BinaryWriter.prototype.rawWriteUint32 = function(value) { - goog.asserts.assert(value == Math.floor(value)); - goog.asserts.assert((value >= 0) && - (value < jspb.BinaryConstants.TWO_TO_32)); - this.temp_.push((value >>> 0) & 0xFF); - this.temp_.push((value >>> 8) & 0xFF); - this.temp_.push((value >>> 16) & 0xFF); - this.temp_.push((value >>> 24) & 0xFF); -}; - - -/** - * Writes a raw 64-bit unsigned integer to the buffer. Numbers outside the - * range [0,2^64) will be truncated. - * @param {number} value The value to write. - */ -jspb.BinaryWriter.prototype.rawWriteUint64 = function(value) { - goog.asserts.assert(value == Math.floor(value)); - goog.asserts.assert((value >= 0) && - (value < jspb.BinaryConstants.TWO_TO_64)); - jspb.utils.splitUint64(value); - this.rawWriteUint32(jspb.utils.split64Low); - this.rawWriteUint32(jspb.utils.split64High); -}; - - -/** - * Writes a raw 8-bit integer to the buffer. Numbers outside the range - * [-2^7,2^7) will be truncated. - * @param {number} value The value to write. - */ -jspb.BinaryWriter.prototype.rawWriteInt8 = function(value) { - goog.asserts.assert(value == Math.floor(value)); - goog.asserts.assert((value >= -128) && (value < 128)); - this.temp_.push((value >>> 0) & 0xFF); -}; - - -/** - * Writes a raw 16-bit integer to the buffer. Numbers outside the range - * [-2^15,2^15) will be truncated. - * @param {number} value The value to write. - */ -jspb.BinaryWriter.prototype.rawWriteInt16 = function(value) { - goog.asserts.assert(value == Math.floor(value)); - goog.asserts.assert((value >= -32768) && (value < 32768)); - this.temp_.push((value >>> 0) & 0xFF); - this.temp_.push((value >>> 8) & 0xFF); -}; - - -/** - * Writes a raw 32-bit integer to the buffer. Numbers outside the range - * [-2^31,2^31) will be truncated. - * @param {number} value The value to write. - */ -jspb.BinaryWriter.prototype.rawWriteInt32 = function(value) { - goog.asserts.assert(value == Math.floor(value)); - goog.asserts.assert((value >= -jspb.BinaryConstants.TWO_TO_31) && - (value < jspb.BinaryConstants.TWO_TO_31)); - this.temp_.push((value >>> 0) & 0xFF); - this.temp_.push((value >>> 8) & 0xFF); - this.temp_.push((value >>> 16) & 0xFF); - this.temp_.push((value >>> 24) & 0xFF); -}; - - -/** - * Writes a raw 64-bit integer to the buffer. Numbers outside the range - * [-2^63,2^63) will be truncated. - * @param {number} value The value to write. - */ -jspb.BinaryWriter.prototype.rawWriteInt64 = function(value) { - goog.asserts.assert(value == Math.floor(value)); - goog.asserts.assert((value >= -jspb.BinaryConstants.TWO_TO_63) && - (value < jspb.BinaryConstants.TWO_TO_63)); - jspb.utils.splitInt64(value); - this.rawWriteUint32(jspb.utils.split64Low); - this.rawWriteUint32(jspb.utils.split64High); -}; - - -/** - * Writes a raw single-precision floating point value to the buffer. Numbers - * requiring more than 32 bits of precision will be truncated. - * @param {number} value The value to write. - */ -jspb.BinaryWriter.prototype.rawWriteFloat = function(value) { - jspb.utils.splitFloat32(value); - this.rawWriteUint32(jspb.utils.split64Low); -}; - - -/** - * Writes a raw double-precision floating point value to the buffer. As this is - * the native format used by JavaScript, no precision will be lost. - * @param {number} value The value to write. - */ -jspb.BinaryWriter.prototype.rawWriteDouble = function(value) { - jspb.utils.splitFloat64(value); - this.rawWriteUint32(jspb.utils.split64Low); - this.rawWriteUint32(jspb.utils.split64High); -}; - - -/** - * Writes a raw boolean value to the buffer as a varint. - * @param {boolean} value The value to write. - */ -jspb.BinaryWriter.prototype.rawWriteBool = function(value) { - goog.asserts.assert(goog.isBoolean(value)); - this.temp_.push(~~value); -}; - - -/** - * Writes an raw enum value to the buffer as a varint. - * @param {number} value The value to write. - */ -jspb.BinaryWriter.prototype.rawWriteEnum = function(value) { - goog.asserts.assert(value == Math.floor(value)); - goog.asserts.assert((value >= -jspb.BinaryConstants.TWO_TO_31) && - (value < jspb.BinaryConstants.TWO_TO_31)); - this.rawWriteSignedVarint32(value); -}; - - -/** - * Writes a raw string value to the buffer. - * @param {string} string The string to write. - */ -jspb.BinaryWriter.prototype.rawWriteUtf8String = function(string) { - for (var i = 0; i < string.length; i++) { - this.temp_.push(string.charCodeAt(i)); - } -}; - - -/** - * Writes an arbitrary raw byte array to the buffer. - * @param {!Uint8Array} bytes The array of bytes to write. - */ -jspb.BinaryWriter.prototype.rawWriteBytes = function(bytes) { - this.appendUint8Array_(bytes); -}; - - -/** - * Writes an arbitrary raw byte array to the buffer. - * @param {!Uint8Array} bytes The array of bytes to write. - * @param {number} start The start of the range to write. - * @param {number} end The end of the range to write. - */ -jspb.BinaryWriter.prototype.rawWriteByteRange = function(bytes, start, end) { - this.appendUint8Array_(bytes.subarray(start, end)); -}; - - -/** - * Writes a 64-bit hash string (8 characters @ 8 bits of data each) to the - * buffer as a varint. - * @param {string} hash The hash to write. - */ -jspb.BinaryWriter.prototype.rawWriteVarintHash64 = function(hash) { - jspb.utils.splitHash64(hash); - this.rawWriteSplitVarint(jspb.utils.split64Low, - jspb.utils.split64High); -}; - - -/** - * Writes a 64-bit hash string (8 characters @ 8 bits of data each) to the - * buffer as a fixed64. - * @param {string} hash The hash to write. - */ -jspb.BinaryWriter.prototype.rawWriteFixedHash64 = function(hash) { - jspb.utils.splitHash64(hash); - this.rawWriteUint32(jspb.utils.split64Low); - this.rawWriteUint32(jspb.utils.split64High); -}; - - -/** * Encodes a (field number, wire type) tuple into a wire-format field header * and stores it in the buffer as a varint. * @param {number} field The field number. @@ -605,11 +272,11 @@ jspb.BinaryWriter.prototype.rawWriteFixedHash64 = function(hash) { * protocol buffer documentation. * @private */ -jspb.BinaryWriter.prototype.rawWriteFieldHeader_ = +jspb.BinaryWriter.prototype.writeFieldHeader_ = function(field, wireType) { goog.asserts.assert(field >= 1 && field == Math.floor(field)); var x = field * 8 + wireType; - this.rawWriteUnsignedVarint32(x); + this.encoder_.writeUnsignedVarint32(x); }; @@ -697,8 +364,8 @@ jspb.BinaryWriter.prototype.writeAny = function(fieldType, field, value) { */ jspb.BinaryWriter.prototype.writeUnsignedVarint32_ = function(field, value) { if (value == null) return; - this.rawWriteFieldHeader_(field, jspb.BinaryConstants.WireType.VARINT); - this.rawWriteSignedVarint32(value); + this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.VARINT); + this.encoder_.writeUnsignedVarint32(value); }; @@ -710,8 +377,21 @@ jspb.BinaryWriter.prototype.writeUnsignedVarint32_ = function(field, value) { */ jspb.BinaryWriter.prototype.writeSignedVarint32_ = function(field, value) { if (value == null) return; - this.rawWriteFieldHeader_(field, jspb.BinaryConstants.WireType.VARINT); - this.rawWriteSignedVarint32(value); + this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.VARINT); + this.encoder_.writeSignedVarint32(value); +}; + + +/** + * Writes a varint field to the buffer without range checking. + * @param {number} field The field number. + * @param {number?} value The value to write. + * @private + */ +jspb.BinaryWriter.prototype.writeUnsignedVarint64_ = function(field, value) { + if (value == null) return; + this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.VARINT); + this.encoder_.writeUnsignedVarint64(value); }; @@ -721,10 +401,10 @@ jspb.BinaryWriter.prototype.writeSignedVarint32_ = function(field, value) { * @param {number?} value The value to write. * @private */ -jspb.BinaryWriter.prototype.writeVarint_ = function(field, value) { +jspb.BinaryWriter.prototype.writeSignedVarint64_ = function(field, value) { if (value == null) return; - this.rawWriteFieldHeader_(field, jspb.BinaryConstants.WireType.VARINT); - this.rawWriteVarint(value); + this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.VARINT); + this.encoder_.writeSignedVarint64(value); }; @@ -736,8 +416,8 @@ jspb.BinaryWriter.prototype.writeVarint_ = function(field, value) { */ jspb.BinaryWriter.prototype.writeZigzagVarint32_ = function(field, value) { if (value == null) return; - this.rawWriteFieldHeader_(field, jspb.BinaryConstants.WireType.VARINT); - this.rawWriteZigzagVarint32(value); + this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.VARINT); + this.encoder_.writeZigzagVarint32(value); }; @@ -747,10 +427,10 @@ jspb.BinaryWriter.prototype.writeZigzagVarint32_ = function(field, value) { * @param {number?} value The value to write. * @private */ -jspb.BinaryWriter.prototype.writeZigzagVarint_ = function(field, value) { +jspb.BinaryWriter.prototype.writeZigzagVarint64_ = function(field, value) { if (value == null) return; - this.rawWriteFieldHeader_(field, jspb.BinaryConstants.WireType.VARINT); - this.rawWriteZigzagVarint(value); + this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.VARINT); + this.encoder_.writeZigzagVarint64(value); }; @@ -793,7 +473,7 @@ jspb.BinaryWriter.prototype.writeInt64 = function(field, value) { if (value == null) return; goog.asserts.assert((value >= -jspb.BinaryConstants.TWO_TO_63) && (value < jspb.BinaryConstants.TWO_TO_63)); - this.writeVarint_(field, value); + this.writeSignedVarint64_(field, value); }; @@ -805,8 +485,8 @@ jspb.BinaryWriter.prototype.writeInt64 = function(field, value) { jspb.BinaryWriter.prototype.writeInt64String = function(field, value) { if (value == null) return; var num = jspb.arith.Int64.fromString(value); - this.rawWriteFieldHeader_(field, jspb.BinaryConstants.WireType.VARINT); - this.rawWriteVarintFromNum(num); + this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.VARINT); + this.encoder_.writeSplitVarint64(num.lo, num.hi); }; @@ -849,7 +529,7 @@ jspb.BinaryWriter.prototype.writeUint64 = function(field, value) { if (value == null) return; goog.asserts.assert((value >= 0) && (value < jspb.BinaryConstants.TWO_TO_64)); - this.writeVarint_(field, value); + this.writeUnsignedVarint64_(field, value); }; @@ -861,8 +541,8 @@ jspb.BinaryWriter.prototype.writeUint64 = function(field, value) { jspb.BinaryWriter.prototype.writeUint64String = function(field, value) { if (value == null) return; var num = jspb.arith.UInt64.fromString(value); - this.rawWriteFieldHeader_(field, jspb.BinaryConstants.WireType.VARINT); - this.rawWriteVarintFromNum(num); + this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.VARINT); + this.encoder_.writeSplitVarint64(num.lo, num.hi); }; @@ -890,7 +570,7 @@ jspb.BinaryWriter.prototype.writeSint64 = function(field, value) { if (value == null) return; goog.asserts.assert((value >= -jspb.BinaryConstants.TWO_TO_63) && (value < jspb.BinaryConstants.TWO_TO_63)); - this.writeZigzagVarint_(field, value); + this.writeZigzagVarint64_(field, value); }; @@ -904,8 +584,8 @@ jspb.BinaryWriter.prototype.writeFixed32 = function(field, value) { if (value == null) return; goog.asserts.assert((value >= 0) && (value < jspb.BinaryConstants.TWO_TO_32)); - this.rawWriteFieldHeader_(field, jspb.BinaryConstants.WireType.FIXED32); - this.rawWriteUint32(value); + this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.FIXED32); + this.encoder_.writeUint32(value); }; @@ -919,8 +599,8 @@ jspb.BinaryWriter.prototype.writeFixed64 = function(field, value) { if (value == null) return; goog.asserts.assert((value >= 0) && (value < jspb.BinaryConstants.TWO_TO_64)); - this.rawWriteFieldHeader_(field, jspb.BinaryConstants.WireType.FIXED64); - this.rawWriteUint64(value); + this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.FIXED64); + this.encoder_.writeUint64(value); }; @@ -934,8 +614,8 @@ jspb.BinaryWriter.prototype.writeSfixed32 = function(field, value) { if (value == null) return; goog.asserts.assert((value >= -jspb.BinaryConstants.TWO_TO_31) && (value < jspb.BinaryConstants.TWO_TO_31)); - this.rawWriteFieldHeader_(field, jspb.BinaryConstants.WireType.FIXED32); - this.rawWriteInt32(value); + this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.FIXED32); + this.encoder_.writeInt32(value); }; @@ -949,8 +629,8 @@ jspb.BinaryWriter.prototype.writeSfixed64 = function(field, value) { if (value == null) return; goog.asserts.assert((value >= -jspb.BinaryConstants.TWO_TO_63) && (value < jspb.BinaryConstants.TWO_TO_63)); - this.rawWriteFieldHeader_(field, jspb.BinaryConstants.WireType.FIXED64); - this.rawWriteInt64(value); + this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.FIXED64); + this.encoder_.writeInt64(value); }; @@ -962,8 +642,8 @@ jspb.BinaryWriter.prototype.writeSfixed64 = function(field, value) { */ jspb.BinaryWriter.prototype.writeFloat = function(field, value) { if (value == null) return; - this.rawWriteFieldHeader_(field, jspb.BinaryConstants.WireType.FIXED32); - this.rawWriteFloat(value); + this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.FIXED32); + this.encoder_.writeFloat(value); }; @@ -975,8 +655,8 @@ jspb.BinaryWriter.prototype.writeFloat = function(field, value) { */ jspb.BinaryWriter.prototype.writeDouble = function(field, value) { if (value == null) return; - this.rawWriteFieldHeader_(field, jspb.BinaryConstants.WireType.FIXED64); - this.rawWriteDouble(value); + this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.FIXED64); + this.encoder_.writeDouble(value); }; @@ -988,8 +668,8 @@ jspb.BinaryWriter.prototype.writeDouble = function(field, value) { jspb.BinaryWriter.prototype.writeBool = function(field, value) { if (value == null) return; goog.asserts.assert(goog.isBoolean(value)); - this.rawWriteFieldHeader_(field, jspb.BinaryConstants.WireType.VARINT); - this.temp_.push(~~value); + this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.VARINT); + this.encoder_.writeBool(value); }; @@ -1002,8 +682,8 @@ jspb.BinaryWriter.prototype.writeEnum = function(field, value) { if (value == null) return; goog.asserts.assert((value >= -jspb.BinaryConstants.TWO_TO_31) && (value < jspb.BinaryConstants.TWO_TO_31)); - this.rawWriteFieldHeader_(field, jspb.BinaryConstants.WireType.VARINT); - this.rawWriteSignedVarint32(value); + this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.VARINT); + this.encoder_.writeSignedVarint32(value); }; @@ -1014,99 +694,41 @@ jspb.BinaryWriter.prototype.writeEnum = function(field, value) { */ jspb.BinaryWriter.prototype.writeString = function(field, value) { if (value == null) return; - this.rawWriteFieldHeader_(field, jspb.BinaryConstants.WireType.DELIMITED); - - // Conversion loop swiped from goog.crypt.stringToUtf8ByteArray. Note that - // 'bytes' will be at least as long as 'value', but could be longer if we - // need to unpack unicode characters. - var bytes = []; - for (var i = 0; i < value.length; i++) { - var c = value.charCodeAt(i); - if (c < 128) { - bytes.push(c); - } else if (c < 2048) { - bytes.push((c >> 6) | 192); - bytes.push((c & 63) | 128); - } else { - bytes.push((c >> 12) | 224); - bytes.push(((c >> 6) & 63) | 128); - bytes.push((c & 63) | 128); - } - } - - this.rawWriteUnsignedVarint32(bytes.length); - this.appendArray_(bytes); + var bookmark = this.beginDelimited_(field); + this.encoder_.writeString(value); + this.endDelimited_(bookmark); }; /** * Writes an arbitrary byte field to the buffer. Note - to match the behavior * of the C++ implementation, empty byte arrays _are_ serialized. - * - * If 'value' is null, this method will try and copy the pre-serialized value - * in 'opt_buffer' if present. - * * @param {number} field The field number. - * @param {jspb.ByteSource} value The array of bytes to write. - * @param {?Uint8Array=} opt_buffer A buffer containing pre-packed values. - * @param {?number=} opt_start The starting point in the above buffer. - * @param {?number=} opt_end The ending point in the above buffer. - * @param {boolean=} opt_stringIsRawBytes If `value` is a string, interpret it - * as a series of raw bytes (codepoints 0--255 inclusive) rather than base64 - * data. - */ -jspb.BinaryWriter.prototype.writeBytes = - function(field, value, opt_buffer, opt_start, opt_end, - opt_stringIsRawBytes) { - if (value != null) { - this.rawWriteFieldHeader_(field, - jspb.BinaryConstants.WireType.DELIMITED); - this.rawWriteUnsignedVarint32(value.length); - this.rawWriteBytes( - jspb.utils.byteSourceToUint8Array(value, opt_stringIsRawBytes)); - } else if ((opt_buffer != null) && (opt_start != null) && (opt_end != null)) { - this.rawWriteByteRange(opt_buffer, opt_start, opt_end); - } -}; - - -/** - * Writes an arbitrary byte field to the buffer, with `opt_stringIsRawBytes` - * flag implicitly true. - * @param {number} field - * @param {jspb.ByteSource} value The array of bytes to write. + * @param {?jspb.ByteSource} value The array of bytes to write. */ -jspb.BinaryWriter.prototype.writeBytesRawString = function(field, value) { - this.writeBytes(field, value, null, null, null, true); +jspb.BinaryWriter.prototype.writeBytes = function(field, value) { + if (value == null) return; + var bytes = jspb.utils.byteSourceToUint8Array(value); + this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.DELIMITED); + this.encoder_.writeUnsignedVarint32(bytes.length); + this.appendUint8Array_(bytes); }; /** * Writes a message to the buffer. - * - * If 'value' is null, this method will try and copy the pre-serialized value - * in 'opt_buffer' if present. - * * @template MessageType * @param {number} field The field number. * @param {?MessageType} value The message to write. * @param {!jspb.WriterFunction} writerCallback Will be invoked with the value * to write and the writer to write it with. - * @param {?Uint8Array=} opt_buffer A buffer containing pre-packed values. - * @param {?number=} opt_start The starting point in the above buffer. - * @param {?number=} opt_end The ending point in the above buffer. */ -jspb.BinaryWriter.prototype.writeMessage = - function(field, value, writerCallback, opt_buffer, opt_start, opt_end) { - if (value !== null) { - var bookmark = this.beginDelimited_(field); - - writerCallback(value, this); - - this.endDelimited_(bookmark); - } else if (opt_buffer && (opt_start != null) && (opt_end != null)) { - this.rawWriteByteRange(opt_buffer, opt_start, opt_end); - } +jspb.BinaryWriter.prototype.writeMessage = function( + field, value, writerCallback) { + if (value == null) return; + var bookmark = this.beginDelimited_(field); + writerCallback(value, this); + this.endDelimited_(bookmark); }; @@ -1120,15 +742,12 @@ jspb.BinaryWriter.prototype.writeMessage = * @param {!jspb.WriterFunction} writerCallback Will be invoked with the value * to write and the writer to write it with. */ -jspb.BinaryWriter.prototype.writeGroup = - function(field, value, writerCallback) { - if (value) { - this.rawWriteFieldHeader_( - field, jspb.BinaryConstants.WireType.START_GROUP); - writerCallback(value, this); - this.rawWriteFieldHeader_( - field, jspb.BinaryConstants.WireType.END_GROUP); - } +jspb.BinaryWriter.prototype.writeGroup = function( + field, value, writerCallback) { + if (value == null) return; + this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.START_GROUP); + writerCallback(value, this); + this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.END_GROUP); }; @@ -1141,8 +760,8 @@ jspb.BinaryWriter.prototype.writeGroup = jspb.BinaryWriter.prototype.writeFixedHash64 = function(field, value) { if (value == null) return; goog.asserts.assert(value.length == 8); - this.rawWriteFieldHeader_(field, jspb.BinaryConstants.WireType.FIXED64); - this.rawWriteFixedHash64(value); + this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.FIXED64); + this.encoder_.writeFixedHash64(value); }; @@ -1155,8 +774,8 @@ jspb.BinaryWriter.prototype.writeFixedHash64 = function(field, value) { jspb.BinaryWriter.prototype.writeVarintHash64 = function(field, value) { if (value == null) return; goog.asserts.assert(value.length == 8); - this.rawWriteFieldHeader_(field, jspb.BinaryConstants.WireType.VARINT); - this.rawWriteVarintHash64(value); + this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.VARINT); + this.encoder_.writeVarintHash64(value); }; @@ -1196,10 +815,26 @@ jspb.BinaryWriter.prototype.writeRepeatedSignedVarint32_ = * @param {?Array.<number>} value The array of ints to write. * @private */ -jspb.BinaryWriter.prototype.writeRepeatedVarint_ = function(field, value) { +jspb.BinaryWriter.prototype.writeRepeatedUnsignedVarint64_ = + function(field, value) { + if (value == null) return; + for (var i = 0; i < value.length; i++) { + this.writeUnsignedVarint64_(field, value[i]); + } +}; + + +/** + * Writes an array of numbers to the buffer as a repeated varint field. + * @param {number} field The field number. + * @param {?Array.<number>} value The array of ints to write. + * @private + */ +jspb.BinaryWriter.prototype.writeRepeatedSignedVarint64_ = + function(field, value) { if (value == null) return; for (var i = 0; i < value.length; i++) { - this.writeVarint_(field, value[i]); + this.writeSignedVarint64_(field, value[i]); } }; @@ -1227,7 +862,7 @@ jspb.BinaryWriter.prototype.writeRepeatedZigzag32_ = function(field, value) { jspb.BinaryWriter.prototype.writeRepeatedZigzag_ = function(field, value) { if (value == null) return; for (var i = 0; i < value.length; i++) { - this.writeZigzagVarint_(field, value[i]); + this.writeZigzagVarint64_(field, value[i]); } }; @@ -1262,7 +897,7 @@ jspb.BinaryWriter.prototype.writeRepeatedInt32String = * @param {?Array.<number>} value The array of ints to write. */ jspb.BinaryWriter.prototype.writeRepeatedInt64 = - jspb.BinaryWriter.prototype.writeRepeatedVarint_; + jspb.BinaryWriter.prototype.writeRepeatedSignedVarint64_; /** @@ -1312,7 +947,7 @@ jspb.BinaryWriter.prototype.writeRepeatedUint32String = * @param {?Array.<number>} value The array of ints to write. */ jspb.BinaryWriter.prototype.writeRepeatedUint64 = - jspb.BinaryWriter.prototype.writeRepeatedVarint_; + jspb.BinaryWriter.prototype.writeRepeatedUnsignedVarint64_; /** @@ -1469,96 +1104,54 @@ jspb.BinaryWriter.prototype.writeRepeatedString = function(field, value) { /** * Writes an array of arbitrary byte fields to the buffer. - * - * If 'value' is null, this method will try and copy the pre-serialized value - * in 'opt_buffer' if present. - * * @param {number} field The field number. - * @param {?Array.<!Uint8Array|string>} value - * The arrays of arrays of bytes to write. - * @param {?Uint8Array=} opt_buffer A buffer containing pre-packed values. - * @param {?number=} opt_start The starting point in the above buffer. - * @param {?number=} opt_end The ending point in the above buffer. - * @param {boolean=} opt_stringIsRawBytes Any values that are strings are - * interpreted as raw bytes rather than base64 data. - */ -jspb.BinaryWriter.prototype.writeRepeatedBytes = - function(field, value, opt_buffer, opt_start, opt_end, - opt_stringIsRawBytes) { - if (value != null) { - for (var i = 0; i < value.length; i++) { - this.writeBytes(field, value[i], null, null, null, opt_stringIsRawBytes); - } - } else if ((opt_buffer != null) && (opt_start != null) && (opt_end != null)) { - this.rawWriteByteRange(opt_buffer, opt_start, opt_end); - } -}; - - -/** - * Writes an array of arbitrary byte fields to the buffer, with - * `opt_stringIsRawBytes` implicitly true. - * @param {number} field - * @param {?Array.<string>} value + * @param {?Array.<!jspb.ByteSource>} value The arrays of arrays of bytes to + * write. */ -jspb.BinaryWriter.prototype.writeRepeatedBytesRawString = - function(field, value) { - this.writeRepeatedBytes(field, value, null, null, null, true); +jspb.BinaryWriter.prototype.writeRepeatedBytes = function(field, value) { + if (value == null) return; + for (var i = 0; i < value.length; i++) { + this.writeBytes(field, value[i]); + } }; /** * Writes an array of messages to the buffer. - * - * If 'value' is null, this method will try and copy the pre-serialized value - * in 'opt_buffer' if present. - * * @template MessageType * @param {number} field The field number. - * @param {?Array.<!MessageType>} value The array of messages to + * @param {?Array.<MessageType>} value The array of messages to * write. * @param {!jspb.WriterFunction} writerCallback Will be invoked with the value * to write and the writer to write it with. - * @param {?Uint8Array=} opt_buffer A buffer containing pre-packed values. - * @param {?number=} opt_start The starting point in the above buffer. - * @param {?number=} opt_end The ending point in the above buffer. - */ -jspb.BinaryWriter.prototype.writeRepeatedMessage = - function(field, value, writerCallback, opt_buffer, opt_start, opt_end) { - if (value) { - for (var i = 0; i < value.length; i++) { - var bookmark = this.beginDelimited_(field); - - writerCallback(value[i], this); - - this.endDelimited_(bookmark); - } - } else if (opt_buffer && (opt_start != null) && (opt_end != null)) { - this.rawWriteByteRange(opt_buffer, opt_start, opt_end); + */ +jspb.BinaryWriter.prototype.writeRepeatedMessage = function( + field, value, writerCallback) { + if (value == null) return; + for (var i = 0; i < value.length; i++) { + var bookmark = this.beginDelimited_(field); + writerCallback(value[i], this); + this.endDelimited_(bookmark); } }; /** * Writes an array of group messages to the buffer. - * * @template MessageType * @param {number} field The field number. - * @param {?Array.<!MessageType>} value The array of messages to + * @param {?Array.<MessageType>} value The array of messages to * write. * @param {!jspb.WriterFunction} writerCallback Will be invoked with the value * to write and the writer to write it with. */ -jspb.BinaryWriter.prototype.writeRepeatedGroup = - function(field, value, writerCallback) { - if (value) { - for (var i = 0; i < value.length; i++) { - this.rawWriteFieldHeader_( - field, jspb.BinaryConstants.WireType.START_GROUP); - writerCallback(value[i], this); - this.rawWriteFieldHeader_( - field, jspb.BinaryConstants.WireType.END_GROUP); - } +jspb.BinaryWriter.prototype.writeRepeatedGroup = function( + field, value, writerCallback) { + if (value == null) return; + for (var i = 0; i < value.length; i++) { + this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.START_GROUP); + writerCallback(value[i], this); + this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.END_GROUP); } }; @@ -1595,123 +1188,108 @@ jspb.BinaryWriter.prototype.writeRepeatedVarintHash64 = /** * Writes an array of numbers to the buffer as a packed varint field. - * - * If 'value' is null, this method will try and copy the pre-serialized value - * in 'opt_buffer' if present. - * * @param {number} field The field number. * @param {?Array.<number>} value The array of ints to write. - * @param {?Uint8Array=} opt_buffer A buffer containing pre-packed values. - * @param {?number=} opt_start The starting point in the above buffer. - * @param {?number=} opt_end The ending point in the above buffer. * @private */ -jspb.BinaryWriter.prototype.writePackedUnsignedVarint32_ = - function(field, value, opt_buffer, opt_start, opt_end) { - if (value != null && value.length) { - var bookmark = this.beginDelimited_(field); - for (var i = 0; i < value.length; i++) { - this.rawWriteUnsignedVarint32(value[i]); - } - this.endDelimited_(bookmark); - } else if ((opt_buffer != null) && (opt_start != null) && (opt_end != null)) { - this.rawWriteByteRange(opt_buffer, opt_start, opt_end); +jspb.BinaryWriter.prototype.writePackedUnsignedVarint32_ = function( + field, value) { + if (value == null || !value.length) return; + var bookmark = this.beginDelimited_(field); + for (var i = 0; i < value.length; i++) { + this.encoder_.writeUnsignedVarint32(value[i]); } + this.endDelimited_(bookmark); }; /** * Writes an array of numbers to the buffer as a packed varint field. - * - * If 'value' is null, this method will try and copy the pre-serialized value - * in 'opt_buffer' if present. - * * @param {number} field The field number. * @param {?Array.<number>} value The array of ints to write. - * @param {?Uint8Array=} opt_buffer A buffer containing pre-packed values. - * @param {?number=} opt_start The starting point in the above buffer. - * @param {?number=} opt_end The ending point in the above buffer. * @private */ -jspb.BinaryWriter.prototype.writePackedSignedVarint32_ = - function(field, value, opt_buffer, opt_start, opt_end) { - if (value != null && value.length) { - var bookmark = this.beginDelimited_(field); - for (var i = 0; i < value.length; i++) { - this.rawWriteSignedVarint32(value[i]); - } - this.endDelimited_(bookmark); - } else if ((opt_buffer != null) && (opt_start != null) && (opt_end != null)) { - this.rawWriteByteRange(opt_buffer, opt_start, opt_end); +jspb.BinaryWriter.prototype.writePackedSignedVarint32_ = function( + field, value) { + if (value == null || !value.length) return; + var bookmark = this.beginDelimited_(field); + for (var i = 0; i < value.length; i++) { + this.encoder_.writeSignedVarint32(value[i]); } + this.endDelimited_(bookmark); }; /** * Writes an array of numbers to the buffer as a packed varint field. - * - * If 'value' is null, this method will try and copy the pre-serialized value - * in 'opt_buffer' if present. - * * @param {number} field The field number. * @param {?Array.<number>} value The array of ints to write. - * @param {?Uint8Array=} opt_buffer A buffer containing pre-packed values. - * @param {?number=} opt_start The starting point in the above buffer. - * @param {?number=} opt_end The ending point in the above buffer. * @private */ -jspb.BinaryWriter.prototype.writePackedVarint_ = - function(field, value, opt_buffer, opt_start, opt_end) { - if (value != null && value.length) { - var bookmark = this.beginDelimited_(field); - for (var i = 0; i < value.length; i++) { - this.rawWriteVarint(value[i]); - } - this.endDelimited_(bookmark); - } else if ((opt_buffer != null) && (opt_start != null) && (opt_end != null)) { - this.rawWriteByteRange(opt_buffer, opt_start, opt_end); +jspb.BinaryWriter.prototype.writePackedUnsignedVarint64_ = function( + field, value) { + if (value == null || !value.length) return; + var bookmark = this.beginDelimited_(field); + for (var i = 0; i < value.length; i++) { + this.encoder_.writeUnsignedVarint64(value[i]); } + this.endDelimited_(bookmark); +}; + + +/** + * Writes an array of numbers to the buffer as a packed varint field. + * @param {number} field The field number. + * @param {?Array.<number>} value The array of ints to write. + * @private + */ +jspb.BinaryWriter.prototype.writePackedSignedVarint64_ = function( + field, value) { + if (value == null || !value.length) return; + var bookmark = this.beginDelimited_(field); + for (var i = 0; i < value.length; i++) { + this.encoder_.writeSignedVarint64(value[i]); + } + this.endDelimited_(bookmark); }; /** * Writes an array of numbers to the buffer as a packed zigzag field. - * - * If 'value' is null, this method will try and copy the pre-serialized value - * in 'opt_buffer' if present. - * * @param {number} field The field number. * @param {?Array.<number>} value The array of ints to write. - * @param {?Uint8Array=} opt_buffer A buffer containing pre-packed values. - * @param {?number=} opt_start The starting point in the above buffer. - * @param {?number=} opt_end The ending point in the above buffer. * @private */ -jspb.BinaryWriter.prototype.writePackedZigzag_ = - function(field, value, opt_buffer, opt_start, opt_end) { - if (value != null && value.length) { - var bookmark = this.beginDelimited_(field); - for (var i = 0; i < value.length; i++) { - this.rawWriteZigzagVarint(value[i]); - } - this.endDelimited_(bookmark); - } else if ((opt_buffer != null) && (opt_start != null) && (opt_end != null)) { - this.rawWriteByteRange(opt_buffer, opt_start, opt_end); +jspb.BinaryWriter.prototype.writePackedZigzag32_ = function(field, value) { + if (value == null || !value.length) return; + var bookmark = this.beginDelimited_(field); + for (var i = 0; i < value.length; i++) { + this.encoder_.writeZigzagVarint32(value[i]); + } + this.endDelimited_(bookmark); +}; + + +/** + * Writes an array of numbers to the buffer as a packed zigzag field. + * @param {number} field The field number. + * @param {?Array.<number>} value The array of ints to write. + * @private + */ +jspb.BinaryWriter.prototype.writePackedZigzag64_ = function(field, value) { + if (value == null || !value.length) return; + var bookmark = this.beginDelimited_(field); + for (var i = 0; i < value.length; i++) { + this.encoder_.writeZigzagVarint64(value[i]); } + this.endDelimited_(bookmark); }; /** * Writes an array of numbers to the buffer as a packed 32-bit int field. - * - * If 'value' is null, this method will try and copy the pre-serialized value - * in 'opt_buffer' if present. - * * @param {number} field The field number. * @param {?Array.<number>} value The array of ints to write. - * @param {?Uint8Array=} opt_buffer A buffer containing pre-packed values. - * @param {?number=} opt_start The starting point in the above buffer. - * @param {?number=} opt_end The ending point in the above buffer. */ jspb.BinaryWriter.prototype.writePackedInt32 = jspb.BinaryWriter.prototype.writePackedSignedVarint32_; @@ -1727,7 +1305,7 @@ jspb.BinaryWriter.prototype.writePackedInt32String = function(field, value) { if (value == null || !value.length) return; var bookmark = this.beginDelimited_(field); for (var i = 0; i < value.length; i++) { - this.rawWriteSignedVarint32(parseInt(value[i], 10)); + this.encoder_.writeSignedVarint32(parseInt(value[i], 10)); } this.endDelimited_(bookmark); }; @@ -1737,12 +1315,9 @@ jspb.BinaryWriter.prototype.writePackedInt32String = function(field, value) { * Writes an array of numbers to the buffer as a packed 64-bit int field. * @param {number} field The field number. * @param {?Array.<number>} value The array of ints to write. - * @param {?Uint8Array=} opt_buffer A buffer containing pre-packed values. - * @param {?number=} opt_start The starting point in the above buffer. - * @param {?number=} opt_end The ending point in the above buffer. */ jspb.BinaryWriter.prototype.writePackedInt64 = - jspb.BinaryWriter.prototype.writePackedVarint_; + jspb.BinaryWriter.prototype.writePackedSignedVarint64_; /** @@ -1757,7 +1332,7 @@ jspb.BinaryWriter.prototype.writePackedInt64String = var bookmark = this.beginDelimited_(field); for (var i = 0; i < value.length; i++) { var num = jspb.arith.Int64.fromString(value[i]); - this.rawWriteVarintFromNum(num); + this.encoder_.writeSplitVarint64(num.lo, num.hi); } this.endDelimited_(bookmark); }; @@ -1765,15 +1340,8 @@ jspb.BinaryWriter.prototype.writePackedInt64String = /** * Writes an array numbers to the buffer as a packed unsigned 32-bit int field. - * - * If 'value' is null, this method will try and copy the pre-serialized value - * in 'opt_buffer' if present. - * * @param {number} field The field number. * @param {?Array.<number>} value The array of ints to write. - * @param {?Uint8Array=} opt_buffer A buffer containing pre-packed values. - * @param {?number=} opt_start The starting point in the above buffer. - * @param {?number=} opt_end The ending point in the above buffer. */ jspb.BinaryWriter.prototype.writePackedUint32 = jspb.BinaryWriter.prototype.writePackedUnsignedVarint32_; @@ -1790,7 +1358,7 @@ jspb.BinaryWriter.prototype.writePackedUint32String = if (value == null || !value.length) return; var bookmark = this.beginDelimited_(field); for (var i = 0; i < value.length; i++) { - this.rawWriteUnsignedVarint32(parseInt(value[i], 10)); + this.encoder_.writeUnsignedVarint32(parseInt(value[i], 10)); } this.endDelimited_(bookmark); }; @@ -1798,18 +1366,11 @@ jspb.BinaryWriter.prototype.writePackedUint32String = /** * Writes an array numbers to the buffer as a packed unsigned 64-bit int field. - * - * If 'value' is null, this method will try and copy the pre-serialized value - * in 'opt_buffer' if present. - * * @param {number} field The field number. * @param {?Array.<number>} value The array of ints to write. - * @param {?Uint8Array=} opt_buffer A buffer containing pre-packed values. - * @param {?number=} opt_start The starting point in the above buffer. - * @param {?number=} opt_end The ending point in the above buffer. */ jspb.BinaryWriter.prototype.writePackedUint64 = - jspb.BinaryWriter.prototype.writePackedVarint_; + jspb.BinaryWriter.prototype.writePackedUnsignedVarint64_; /** @@ -1824,7 +1385,7 @@ jspb.BinaryWriter.prototype.writePackedUint64String = var bookmark = this.beginDelimited_(field); for (var i = 0; i < value.length; i++) { var num = jspb.arith.UInt64.fromString(value[i]); - this.rawWriteVarintFromNum(num); + this.encoder_.writeSplitVarint64(num.lo, num.hi); } this.endDelimited_(bookmark); }; @@ -1832,267 +1393,154 @@ jspb.BinaryWriter.prototype.writePackedUint64String = /** * Writes an array numbers to the buffer as a packed signed 32-bit int field. - * - * If 'value' is null, this method will try and copy the pre-serialized value - * in 'opt_buffer' if present. - * * @param {number} field The field number. * @param {?Array.<number>} value The array of ints to write. - * @param {?Uint8Array=} opt_buffer A buffer containing pre-packed values. - * @param {?number=} opt_start The starting point in the above buffer. - * @param {?number=} opt_end The ending point in the above buffer. */ jspb.BinaryWriter.prototype.writePackedSint32 = - jspb.BinaryWriter.prototype.writePackedZigzag_; + jspb.BinaryWriter.prototype.writePackedZigzag32_; /** * Writes an array numbers to the buffer as a packed signed 64-bit int field. - * - * If 'value' is null, this method will try and copy the pre-serialized value - * in 'opt_buffer' if present. - * * @param {number} field The field number. * @param {?Array.<number>} value The array of ints to write. - * @param {?Uint8Array=} opt_buffer A buffer containing pre-packed values. - * @param {?number=} opt_start The starting point in the above buffer. - * @param {?number=} opt_end The ending point in the above buffer. */ jspb.BinaryWriter.prototype.writePackedSint64 = - jspb.BinaryWriter.prototype.writePackedZigzag_; + jspb.BinaryWriter.prototype.writePackedZigzag64_; /** * Writes an array of numbers to the buffer as a packed fixed32 field. - * - * If 'value' is null, this method will try and copy the pre-serialized value - * in 'opt_buffer' if present. - * * @param {number} field The field number. * @param {?Array.<number>} value The array of ints to write. - * @param {?Uint8Array=} opt_buffer A buffer containing pre-packed values. - * @param {?number=} opt_start The starting point in the above buffer. - * @param {?number=} opt_end The ending point in the above buffer. - */ -jspb.BinaryWriter.prototype.writePackedFixed32 = - function(field, value, opt_buffer, opt_start, opt_end) { - if (value != null && value.length) { - this.rawWriteFieldHeader_(field, jspb.BinaryConstants.WireType.DELIMITED); - this.rawWriteUnsignedVarint32(value.length * 4); - for (var i = 0; i < value.length; i++) { - this.rawWriteUint32(value[i]); - } - } else if ((opt_buffer != null) && (opt_start != null) && (opt_end != null)) { - this.rawWriteByteRange(opt_buffer, opt_start, opt_end); + */ +jspb.BinaryWriter.prototype.writePackedFixed32 = function(field, value) { + if (value == null || !value.length) return; + this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.DELIMITED); + this.encoder_.writeUnsignedVarint32(value.length * 4); + for (var i = 0; i < value.length; i++) { + this.encoder_.writeUint32(value[i]); } }; /** * Writes an array of numbers to the buffer as a packed fixed64 field. - * - * If 'value' is null, this method will try and copy the pre-serialized value - * in 'opt_buffer' if present. - * * @param {number} field The field number. * @param {?Array.<number>} value The array of ints to write. - * @param {?Uint8Array=} opt_buffer A buffer containing pre-packed values. - * @param {?number=} opt_start The starting point in the above buffer. - * @param {?number=} opt_end The ending point in the above buffer. - */ -jspb.BinaryWriter.prototype.writePackedFixed64 = - function(field, value, opt_buffer, opt_start, opt_end) { - if (value != null && value.length) { - this.rawWriteFieldHeader_(field, jspb.BinaryConstants.WireType.DELIMITED); - this.rawWriteUnsignedVarint32(value.length * 8); - for (var i = 0; i < value.length; i++) { - this.rawWriteUint64(value[i]); - } - } else if ((opt_buffer != null) && (opt_start != null) && (opt_end != null)) { - this.rawWriteByteRange(opt_buffer, opt_start, opt_end); + */ +jspb.BinaryWriter.prototype.writePackedFixed64 = function(field, value) { + if (value == null || !value.length) return; + this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.DELIMITED); + this.encoder_.writeUnsignedVarint32(value.length * 8); + for (var i = 0; i < value.length; i++) { + this.encoder_.writeUint64(value[i]); } }; /** * Writes an array of numbers to the buffer as a packed sfixed32 field. - * - * If 'value' is null, this method will try and copy the pre-serialized value - * in 'opt_buffer' if present. - * * @param {number} field The field number. * @param {?Array.<number>} value The array of ints to write. - * @param {?Uint8Array=} opt_buffer A buffer containing pre-packed values. - * @param {?number=} opt_start The starting point in the above buffer. - * @param {?number=} opt_end The ending point in the above buffer. - */ -jspb.BinaryWriter.prototype.writePackedSfixed32 = - function(field, value, opt_buffer, opt_start, opt_end) { - if (value != null && value.length) { - this.rawWriteFieldHeader_(field, jspb.BinaryConstants.WireType.DELIMITED); - this.rawWriteUnsignedVarint32(value.length * 4); - for (var i = 0; i < value.length; i++) { - this.rawWriteInt32(value[i]); - } - } else if ((opt_buffer != null) && (opt_start != null) && (opt_end != null)) { - this.rawWriteByteRange(opt_buffer, opt_start, opt_end); + */ +jspb.BinaryWriter.prototype.writePackedSfixed32 = function(field, value) { + if (value == null || !value.length) return; + this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.DELIMITED); + this.encoder_.writeUnsignedVarint32(value.length * 4); + for (var i = 0; i < value.length; i++) { + this.encoder_.writeInt32(value[i]); } }; /** * Writes an array of numbers to the buffer as a packed sfixed64 field. - * - * If 'value' is null, this method will try and copy the pre-serialized value - * in 'opt_buffer' if present. - * * @param {number} field The field number. * @param {?Array.<number>} value The array of ints to write. - * @param {?Uint8Array=} opt_buffer A buffer containing pre-packed values. - * @param {?number=} opt_start The starting point in the above buffer. - * @param {?number=} opt_end The ending point in the above buffer. - */ -jspb.BinaryWriter.prototype.writePackedSfixed64 = - function(field, value, opt_buffer, opt_start, opt_end) { - if (value != null) { - this.rawWriteFieldHeader_(field, jspb.BinaryConstants.WireType.DELIMITED); - this.rawWriteUnsignedVarint32(value.length * 8); - for (var i = 0; i < value.length; i++) { - this.rawWriteInt64(value[i]); - } - } else if ((opt_buffer != null) && (opt_start != null) && (opt_end != null)) { - this.rawWriteByteRange(opt_buffer, opt_start, opt_end); + */ +jspb.BinaryWriter.prototype.writePackedSfixed64 = function(field, value) { + if (value == null || !value.length) return; + this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.DELIMITED); + this.encoder_.writeUnsignedVarint32(value.length * 8); + for (var i = 0; i < value.length; i++) { + this.encoder_.writeInt64(value[i]); } }; /** * Writes an array of numbers to the buffer as a packed float field. - * - * If 'value' is null, this method will try and copy the pre-serialized value - * in 'opt_buffer' if present. - * * @param {number} field The field number. * @param {?Array.<number>} value The array of ints to write. - * @param {?Uint8Array=} opt_buffer A buffer containing pre-packed values. - * @param {?number=} opt_start The starting point in the above buffer. - * @param {?number=} opt_end The ending point in the above buffer. - */ -jspb.BinaryWriter.prototype.writePackedFloat = - function(field, value, opt_buffer, opt_start, opt_end) { - if (value != null && value.length) { - this.rawWriteFieldHeader_(field, jspb.BinaryConstants.WireType.DELIMITED); - this.rawWriteUnsignedVarint32(value.length * 4); - for (var i = 0; i < value.length; i++) { - this.rawWriteFloat(value[i]); - } - } else if ((opt_buffer != null) && (opt_start != null) && (opt_end != null)) { - this.rawWriteByteRange(opt_buffer, opt_start, opt_end); + */ +jspb.BinaryWriter.prototype.writePackedFloat = function(field, value) { + if (value == null || !value.length) return; + this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.DELIMITED); + this.encoder_.writeUnsignedVarint32(value.length * 4); + for (var i = 0; i < value.length; i++) { + this.encoder_.writeFloat(value[i]); } }; /** * Writes an array of numbers to the buffer as a packed double field. - * - * If 'value' is null, this method will try and copy the pre-serialized value - * in 'opt_buffer' if present. - * * @param {number} field The field number. * @param {?Array.<number>} value The array of ints to write. - * @param {?Uint8Array=} opt_buffer A buffer containing pre-packed values. - * @param {?number=} opt_start The starting point in the above buffer. - * @param {?number=} opt_end The ending point in the above buffer. - */ -jspb.BinaryWriter.prototype.writePackedDouble = - function(field, value, opt_buffer, opt_start, opt_end) { - if (value != null && value.length) { - this.rawWriteFieldHeader_(field, jspb.BinaryConstants.WireType.DELIMITED); - this.rawWriteUnsignedVarint32(value.length * 8); - for (var i = 0; i < value.length; i++) { - this.rawWriteDouble(value[i]); - } - } else if ((opt_buffer != null) && (opt_start != null) && (opt_end != null)) { - this.rawWriteByteRange(opt_buffer, opt_start, opt_end); + */ +jspb.BinaryWriter.prototype.writePackedDouble = function(field, value) { + if (value == null || !value.length) return; + this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.DELIMITED); + this.encoder_.writeUnsignedVarint32(value.length * 8); + for (var i = 0; i < value.length; i++) { + this.encoder_.writeDouble(value[i]); } }; /** * Writes an array of booleans to the buffer as a packed bool field. - * - * If 'value' is null, this method will try and copy the pre-serialized value - * in 'opt_buffer' if present. - * * @param {number} field The field number. * @param {?Array.<boolean>} value The array of ints to write. - * @param {?Uint8Array=} opt_buffer A buffer containing pre-packed values. - * @param {?number=} opt_start The starting point in the above buffer. - * @param {?number=} opt_end The ending point in the above buffer. - */ -jspb.BinaryWriter.prototype.writePackedBool = - function(field, value, opt_buffer, opt_start, opt_end) { - if (value != null && value.length) { - this.rawWriteFieldHeader_(field, jspb.BinaryConstants.WireType.DELIMITED); - this.rawWriteUnsignedVarint32(value.length); - for (var i = 0; i < value.length; i++) { - this.rawWriteBool(value[i]); - } - } else if ((opt_buffer != null) && (opt_start != null) && (opt_end != null)) { - this.rawWriteByteRange(opt_buffer, opt_start, opt_end); + */ +jspb.BinaryWriter.prototype.writePackedBool = function(field, value) { + if (value == null || !value.length) return; + this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.DELIMITED); + this.encoder_.writeUnsignedVarint32(value.length); + for (var i = 0; i < value.length; i++) { + this.encoder_.writeBool(value[i]); } }; /** * Writes an array of enums to the buffer as a packed enum field. - * - * If 'value' is null, this method will try and copy the pre-serialized value - * in 'opt_buffer' if present. - * * @param {number} field The field number. * @param {?Array.<number>} value The array of ints to write. - * @param {?Uint8Array=} opt_buffer A buffer containing pre-packed values. - * @param {?number=} opt_start The starting point in the above buffer. - * @param {?number=} opt_end The ending point in the above buffer. */ -jspb.BinaryWriter.prototype.writePackedEnum = - function(field, value, opt_buffer, opt_start, opt_end) { - if (value != null && value.length) { - var bookmark = this.beginDelimited_(field); - for (var i = 0; i < value.length; i++) { - this.rawWriteEnum(value[i]); - } - this.endDelimited_(bookmark); - } else if ((opt_buffer != null) && (opt_start != null) && (opt_end != null)) { - this.rawWriteByteRange(opt_buffer, opt_start, opt_end); +jspb.BinaryWriter.prototype.writePackedEnum = function(field, value) { + if (value == null || !value.length) return; + var bookmark = this.beginDelimited_(field); + for (var i = 0; i < value.length; i++) { + this.encoder_.writeEnum(value[i]); } + this.endDelimited_(bookmark); }; /** * Writes a 64-bit hash string field (8 characters @ 8 bits of data each) to * the buffer. - * - * If 'value' is null, this method will try and copy the pre-serialized value - * in 'opt_buffer' if present. - * * @param {number} field The field number. * @param {?Array.<string>} value The array of hashes to write. - * @param {?Uint8Array=} opt_buffer A buffer containing pre-packed values. - * @param {?number=} opt_start The starting point in the above buffer. - * @param {?number=} opt_end The ending point in the above buffer. - */ -jspb.BinaryWriter.prototype.writePackedFixedHash64 = - function(field, value, opt_buffer, opt_start, opt_end) { - if (value != null && value.length) { - this.rawWriteFieldHeader_(field, jspb.BinaryConstants.WireType.DELIMITED); - this.rawWriteUnsignedVarint32(value.length * 8); - for (var i = 0; i < value.length; i++) { - this.rawWriteFixedHash64(value[i]); - } - } else if ((opt_buffer != null) && (opt_start != null) && (opt_end != null)) { - this.rawWriteByteRange(opt_buffer, opt_start, opt_end); + */ +jspb.BinaryWriter.prototype.writePackedFixedHash64 = function(field, value) { + if (value == null || !value.length) return; + this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.DELIMITED); + this.encoder_.writeUnsignedVarint32(value.length * 8); + for (var i = 0; i < value.length; i++) { + this.encoder_.writeFixedHash64(value[i]); } }; @@ -2100,25 +1548,14 @@ jspb.BinaryWriter.prototype.writePackedFixedHash64 = /** * Writes a 64-bit hash string field (8 characters @ 8 bits of data each) to * the buffer. - * - * If 'value' is null, this method will try and copy the pre-serialized value - * in 'opt_buffer' if present. - * * @param {number} field The field number. * @param {?Array.<string>} value The array of hashes to write. - * @param {?Uint8Array=} opt_buffer A buffer containing pre-packed values. - * @param {?number=} opt_start The starting point in the above buffer. - * @param {?number=} opt_end The ending point in the above buffer. */ -jspb.BinaryWriter.prototype.writePackedVarintHash64 = - function(field, value, opt_buffer, opt_start, opt_end) { - if (value != null && value.length) { - var bookmark = this.beginDelimited_(field); - for (var i = 0; i < value.length; i++) { - this.rawWriteVarintHash64(value[i]); - } - this.endDelimited_(bookmark); - } else if ((opt_buffer != null) && (opt_start != null) && (opt_end != null)) { - this.rawWriteByteRange(opt_buffer, opt_start, opt_end); +jspb.BinaryWriter.prototype.writePackedVarintHash64 = function(field, value) { + if (value == null || !value.length) return; + var bookmark = this.beginDelimited_(field); + for (var i = 0; i < value.length; i++) { + this.encoder_.writeVarintHash64(value[i]); } + this.endDelimited_(bookmark); }; diff --git a/js/debug.js b/js/debug.js index 48389118..3701a095 100644 --- a/js/debug.js +++ b/js/debug.js @@ -68,7 +68,7 @@ jspb.debug.dump = function(message) { * Recursively introspects a message and the values its getters return to * make a best effort in creating a human readable representation of the * message. - * @param {*} thing A jspb.Message, Array or primitive type to dump. + * @param {?} thing A jspb.Message, Array or primitive type to dump. * @return {*} * @private */ diff --git a/js/debug_test.js b/js/debug_test.js index d7bf3768..01cbf891 100644 --- a/js/debug_test.js +++ b/js/debug_test.js @@ -41,6 +41,7 @@ goog.require('proto.jspb.test.IsExtension'); goog.require('proto.jspb.test.Simple1'); + describe('debugTest', function() { it('testSimple1', function() { if (COMPILED) { diff --git a/js/message.js b/js/message.js index cef9aefd..813e6b8c 100644 --- a/js/message.js +++ b/js/message.js @@ -39,8 +39,8 @@ goog.provide('jspb.Message'); goog.require('goog.array'); goog.require('goog.asserts'); +goog.require('goog.crypt.base64'); goog.require('goog.json'); -goog.require('goog.object'); // Not needed in compilation units that have no protos with xids. goog.forwardDeclare('xid.String'); @@ -120,6 +120,14 @@ jspb.ExtensionFieldInfo = function(fieldNumber, fieldName, ctor, toObjectFn, /** + * @return {boolean} Does this field represent a sub Message? + */ +jspb.ExtensionFieldInfo.prototype.isMessageType = function() { + return !!this.ctor; +}; + + +/** * Base class for all JsPb messages. * @constructor * @struct @@ -166,6 +174,14 @@ goog.define('jspb.Message.MINIMIZE_MEMORY_ALLOCATIONS', COMPILED); /** + * Does this browser support Uint8Aray typed arrays? + * @type {boolean} + * @private + */ +jspb.Message.SUPPORTS_UINT8ARRAY_ = (typeof Uint8Array == 'function'); + + +/** * The internal data array. * @type {!Array} * @protected @@ -211,6 +227,14 @@ jspb.Message.prototype.messageId_; /** + * Repeated float or double fields which have been converted to include only + * numbers and not strings holding "NaN", "Infinity" and "-Infinity". + * @private {!Object<number,boolean>|undefined} + */ +jspb.Message.prototype.convertedFloatingPointFields_; + + +/** * The xid of this proto type (The same for all instances of a proto). Provides * a way to identify a proto by stable obfuscated name. * @see {xid}. @@ -284,6 +308,8 @@ jspb.Message.initialize = function( msg.arrayIndexOffset_ = messageId === 0 ? -1 : 0; msg.array = data; jspb.Message.materializeExtensionObject_(msg, suggestedPivot); + msg.convertedFloatingPointFields_ = {}; + if (repeatedFields) { for (var i = 0; i < repeatedFields.length; i++) { var fieldNumber = repeatedFields[i]; @@ -419,8 +445,9 @@ jspb.Message.toObjectList = function(field, toObjectFn, opt_includeInstance) { * @param {!jspb.Message} proto The proto whose extensions to convert. * @param {!Object} obj The Soy object to add converted extension data to. * @param {!Object} extensions The proto class' registered extensions. - * @param {function(jspb.ExtensionFieldInfo) : *} getExtensionFn The proto - * class' getExtension function. Passed for effective dead code removal. + * @param {function(this:?, jspb.ExtensionFieldInfo) : *} getExtensionFn + * The proto class' getExtension function. Passed for effective dead code + * removal. * @param {boolean=} opt_includeInstance Whether to include the JSPB instance * for transitional soy proto support: http://goto/soy-param-migration */ @@ -472,7 +499,7 @@ jspb.Message.serializeBinaryExtensions = function(proto, writer, extensions, } var value = getExtensionFn.call(proto, fieldInfo); if (value) { - if (fieldInfo.ctor) { // is this a message type? + if (fieldInfo.isMessageType()) { // If the message type of the extension was generated without binary // support, there may not be a binary message serializer function, and // we can't know when we codegen the extending message that the extended @@ -517,8 +544,7 @@ jspb.Message.readBinaryExtension = function(msg, reader, extensions, } var value; - if (fieldInfo.ctor) { - // Message type. + if (fieldInfo.isMessageType()) { value = new fieldInfo.ctor(); fieldInfo.binaryReaderFn.call( reader, value, fieldInfo.binaryMessageDeserializeFn); @@ -567,6 +593,130 @@ jspb.Message.getField = function(msg, fieldNumber) { /** + * Gets the value of an optional float or double field. + * @param {!jspb.Message} msg A jspb proto. + * @param {number} fieldNumber The field number. + * @return {?number|undefined} The field's value. + * @protected + */ +jspb.Message.getOptionalFloatingPointField = function(msg, fieldNumber) { + var value = jspb.Message.getField(msg, fieldNumber); + // Converts "NaN", "Infinity" and "-Infinity" to their corresponding numbers. + return value == null ? value : +value; +}; + + +/** + * Gets the value of a repeated float or double field. + * @param {!jspb.Message} msg A jspb proto. + * @param {number} fieldNumber The field number. + * @return {!Array<number>} The field's value. + * @protected + */ +jspb.Message.getRepeatedFloatingPointField = function(msg, fieldNumber) { + var values = jspb.Message.getField(msg, fieldNumber); + if (!msg.convertedFloatingPointFields_) { + msg.convertedFloatingPointFields_ = {}; + } + if (!msg.convertedFloatingPointFields_[fieldNumber]) { + for (var i = 0; i < values.length; i++) { + // Converts "NaN", "Infinity" and "-Infinity" to their corresponding + // numbers. + values[i] = +values[i]; + } + msg.convertedFloatingPointFields_[fieldNumber] = true; + } + return /** @type {!Array<number>} */ (values); +}; + + +/** + * Coerce a 'bytes' field to a base 64 string. + * @param {string|Uint8Array|null} value + * @return {?string} The field's coerced value. + */ +jspb.Message.bytesAsB64 = function(value) { + if (value == null || goog.isString(value)) { + return value; + } + if (jspb.Message.SUPPORTS_UINT8ARRAY_ && value instanceof Uint8Array) { + return goog.crypt.base64.encodeByteArray(value); + } + goog.asserts.fail('Cannot coerce to b64 string: ' + goog.typeOf(value)); + return null; +}; + + +/** + * Coerce a 'bytes' field to a Uint8Array byte buffer. + * Note that Uint8Array is not supported on IE versions before 10 nor on Opera + * Mini. @see http://caniuse.com/Uint8Array + * @param {string|Uint8Array|null} value + * @return {?Uint8Array} The field's coerced value. + */ +jspb.Message.bytesAsU8 = function(value) { + if (value == null || value instanceof Uint8Array) { + return value; + } + if (goog.isString(value)) { + return goog.crypt.base64.decodeStringToUint8Array(value); + } + goog.asserts.fail('Cannot coerce to Uint8Array: ' + goog.typeOf(value)); + return null; +}; + + +/** + * Coerce a repeated 'bytes' field to an array of base 64 strings. + * Note: the returned array should be treated as immutable. + * @param {!Array<string>|!Array<!Uint8Array>} value + * @return {!Array<string?>} The field's coerced value. + */ +jspb.Message.bytesListAsB64 = function(value) { + jspb.Message.assertConsistentTypes_(value); + if (!value.length || goog.isString(value[0])) { + return /** @type {!Array<string>} */ (value); + } + return goog.array.map(value, jspb.Message.bytesAsB64); +}; + + +/** + * Coerce a repeated 'bytes' field to an array of Uint8Array byte buffers. + * Note: the returned array should be treated as immutable. + * Note that Uint8Array is not supported on IE versions before 10 nor on Opera + * Mini. @see http://caniuse.com/Uint8Array + * @param {!Array<string>|!Array<!Uint8Array>} value + * @return {!Array<Uint8Array?>} The field's coerced value. + */ +jspb.Message.bytesListAsU8 = function(value) { + jspb.Message.assertConsistentTypes_(value); + if (!value.length || value[0] instanceof Uint8Array) { + return /** @type {!Array<!Uint8Array>} */ (value); + } + return goog.array.map(value, jspb.Message.bytesAsU8); +}; + + +/** + * Asserts that all elements of an array are of the same type. + * @param {Array?} array The array to test. + * @private + */ +jspb.Message.assertConsistentTypes_ = function(array) { + if (goog.DEBUG && array && array.length > 1) { + var expected = goog.typeOf(array[0]); + goog.array.forEach(array, function(e) { + if (goog.typeOf(e) != expected) { + goog.asserts.fail('Inconsistent type in JSPB repeated field array. ' + + 'Got ' + goog.typeOf(e) + ' expected ' + expected); + } + }); + } +}; + + +/** * Gets the value of a non-extension primitive field, with proto3 (non-nullable * primitives) semantics. Returns `defaultValue` if the field is not otherwise * set. @@ -841,7 +991,7 @@ jspb.Message.prototype.getExtension = function(fieldInfo) { } var fieldNumber = fieldInfo.fieldIndex; if (fieldInfo.isRepeated) { - if (fieldInfo.ctor) { + if (fieldInfo.isMessageType()) { if (!this.wrappers_[fieldNumber]) { this.wrappers_[fieldNumber] = goog.array.map(this.extensionObject_[fieldNumber] || [], @@ -854,7 +1004,7 @@ jspb.Message.prototype.getExtension = function(fieldInfo) { return this.extensionObject_[fieldNumber]; } } else { - if (fieldInfo.ctor) { + if (fieldInfo.isMessageType()) { if (!this.wrappers_[fieldNumber] && this.extensionObject_[fieldNumber]) { this.wrappers_[fieldNumber] = new fieldInfo.ctor( /** @type {Array|undefined} */ ( @@ -871,7 +1021,8 @@ jspb.Message.prototype.getExtension = function(fieldInfo) { /** * Sets the value of the extension field in the extended object. * @param {jspb.ExtensionFieldInfo} fieldInfo Specifies the field to set. - * @param {jspb.Message|string|number|boolean|Array} value The value to set. + * @param {jspb.Message|string|Uint8Array|number|boolean|Array?} value The value + * to set. */ jspb.Message.prototype.setExtension = function(fieldInfo, value) { if (!this.wrappers_) { @@ -881,7 +1032,7 @@ jspb.Message.prototype.setExtension = function(fieldInfo, value) { var fieldNumber = fieldInfo.fieldIndex; if (fieldInfo.isRepeated) { value = value || []; - if (fieldInfo.ctor) { + if (fieldInfo.isMessageType()) { this.wrappers_[fieldNumber] = value; this.extensionObject_[fieldNumber] = goog.array.map( /** @type {Array<jspb.Message>} */ (value), function(msg) { @@ -891,7 +1042,7 @@ jspb.Message.prototype.setExtension = function(fieldInfo, value) { this.extensionObject_[fieldNumber] = value; } } else { - if (fieldInfo.ctor) { + if (fieldInfo.isMessageType()) { this.wrappers_[fieldNumber] = value; this.extensionObject_[fieldNumber] = value ? value.toArray() : value; } else { @@ -958,49 +1109,110 @@ jspb.Message.equals = function(m1, m2) { /** + * Compares two message extension fields recursively. + * @param {!Object} extension1 The first field. + * @param {!Object} extension2 The second field. + * @return {boolean} true if the extensions are null/undefined, or otherwise + * equal. + */ +jspb.Message.compareExtensions = function(extension1, extension2) { + extension1 = extension1 || {}; + extension2 = extension2 || {}; + + var keys = {}; + for (var name in extension1) { + keys[name] = 0; + } + for (var name in extension2) { + keys[name] = 0; + } + for (name in keys) { + if (!jspb.Message.compareFields(extension1[name], extension2[name])) { + return false; + } + } + return true; +}; + + +/** * Compares two message fields recursively. * @param {*} field1 The first field. * @param {*} field2 The second field. * @return {boolean} true if the fields are null/undefined, or otherwise equal. */ jspb.Message.compareFields = function(field1, field2) { - if (goog.isObject(field1) && goog.isObject(field2)) { - var keys = {}, name, extensionObject1, extensionObject2; - for (name in field1) { - field1.hasOwnProperty(name) && (keys[name] = 0); - } - for (name in field2) { - field2.hasOwnProperty(name) && (keys[name] = 0); + // If the fields are trivially equal, they're equal. + if (field1 == field2) return true; + + // If the fields aren't trivially equal and one of them isn't an object, + // they can't possibly be equal. + if (!goog.isObject(field1) || !goog.isObject(field2)) { + return false; + } + + // We have two objects. If they're different types, they're not equal. + field1 = /** @type {!Object} */(field1); + field2 = /** @type {!Object} */(field2); + if (field1.constructor != field2.constructor) return false; + + // If both are Uint8Arrays, compare them element-by-element. + if (jspb.Message.SUPPORTS_UINT8ARRAY_ && field1.constructor === Uint8Array) { + var bytes1 = /** @type {!Uint8Array} */(field1); + var bytes2 = /** @type {!Uint8Array} */(field2); + if (bytes1.length != bytes2.length) return false; + for (var i = 0; i < bytes1.length; i++) { + if (bytes1[i] != bytes2[i]) return false; } - for (name in keys) { - var val1 = field1[name], val2 = field2[name]; - if (goog.isObject(val1) && !goog.isArray(val1)) { - if (extensionObject1 !== undefined) { - throw new Error('invalid jspb state'); - } - extensionObject1 = goog.object.isEmpty(val1) ? undefined : val1; + return true; + } + + // If they're both Arrays, compare them element by element except for the + // optional extension objects at the end, which we compare separately. + if (field1.constructor === Array) { + var extension1 = undefined; + var extension2 = undefined; + + var length = Math.max(field1.length, field2.length); + for (var i = 0; i < length; i++) { + var val1 = field1[i]; + var val2 = field2[i]; + + if (val1 && (val1.constructor == Object)) { + goog.asserts.assert(extension1 === undefined); + goog.asserts.assert(i === field1.length - 1); + extension1 = val1; val1 = undefined; } - if (goog.isObject(val2) && !goog.isArray(val2)) { - if (extensionObject2 !== undefined) { - throw new Error('invalid jspb state'); - } - extensionObject2 = goog.object.isEmpty(val2) ? undefined : val2; + + if (val2 && (val2.constructor == Object)) { + goog.asserts.assert(extension2 === undefined); + goog.asserts.assert(i === field2.length - 1); + extension2 = val2; val2 = undefined; } + if (!jspb.Message.compareFields(val1, val2)) { return false; } } - if (extensionObject1 || extensionObject2) { - return jspb.Message.compareFields(extensionObject1, extensionObject2); + + if (extension1 || extension2) { + extension1 = extension1 || {}; + extension2 = extension2 || {}; + return jspb.Message.compareExtensions(extension1, extension2); } + return true; } - // Primitive fields, null and undefined compare as equal. - // This also forces booleans and 0/1 to compare as equal to ensure - // compatibility with the jspb serializer. - return field1 == field2; + + // If they're both plain Objects (i.e. extensions), compare them as + // extensions. + if (field1.constructor === Object) { + return jspb.Message.compareExtensions(field1, field2); + } + + throw new Error('Invalid type in JSPB array'); }; diff --git a/js/message_test.js b/js/message_test.js index f572188e..01add5f1 100644 --- a/js/message_test.js +++ b/js/message_test.js @@ -53,6 +53,8 @@ goog.require('proto.jspb.test.Complex'); goog.require('proto.jspb.test.DefaultValues'); goog.require('proto.jspb.test.Empty'); goog.require('proto.jspb.test.EnumContainer'); +goog.require('proto.jspb.test.floatingMsgField'); +goog.require('proto.jspb.test.FloatingPointFields'); goog.require('proto.jspb.test.floatingStrField'); goog.require('proto.jspb.test.HasExtensions'); goog.require('proto.jspb.test.IndirectExtension'); @@ -60,7 +62,6 @@ goog.require('proto.jspb.test.IsExtension'); goog.require('proto.jspb.test.OptionalFields'); goog.require('proto.jspb.test.OuterEnum'); goog.require('proto.jspb.test.OuterMessage.Complex'); -goog.require('proto.jspb.test.simple1'); goog.require('proto.jspb.test.Simple1'); goog.require('proto.jspb.test.Simple2'); goog.require('proto.jspb.test.SpecialCases'); @@ -74,7 +75,7 @@ goog.require('proto.jspb.test.TestReservedNamesExtension'); // CommonJS-LoadFromFile: test2_pb proto.jspb.test goog.require('proto.jspb.test.ExtensionMessage'); goog.require('proto.jspb.test.TestExtensionsMessage'); -goog.require('proto.jspb.test.floatingMsgField'); + @@ -98,12 +99,6 @@ describe('Message test suite', function() { assertEquals('some_bytes', data.getBytesField()); }); - it('testNestedMessage', function() { - var msg = new proto.jspb.test.OuterMessage.Complex(); - msg.setInnerComplexField(5); - assertObjectEquals({innerComplexField: 5}, msg.toObject()); - }); - it('testComplexConversion', function() { var data1 = ['a',,, [, 11], [[, 22], [, 33]],, ['s1', 's2'],, 1]; var data2 = ['a',,, [, 11], [[, 22], [, 33]],, ['s1', 's2'],, 1]; @@ -160,6 +155,13 @@ describe('Message test suite', function() { }); + it('testNestedComplexMessage', function() { + // Instantiate the message and set a unique field, just to ensure that we + // are not getting jspb.test.Complex instead. + var msg = new proto.jspb.test.OuterMessage.Complex(); + msg.setInnerComplexField(5); + }); + it('testSpecialCases', function() { // Note: Some property names are reserved in JavaScript. // These names are converted to the Js property named pb_<reserved_name>. @@ -544,12 +546,6 @@ describe('Message test suite', function() { extendable.setExtension(proto.jspb.test.IndirectExtension.str, null); assertNull(extendable.getExtension(proto.jspb.test.IndirectExtension.str)); - // These assertions will only work properly in uncompiled mode. - // Extension fields defined on proto2 Descriptor messages are filtered out. - - // TODO(haberman): codegen changes to properly ignore descriptor.proto - // extensions need to be merged from google3. - // assertUndefined(proto.jspb.test.IsExtension['simpleOption']); // Extension fields with jspb.ignore = true are ignored. assertUndefined(proto.jspb.test.IndirectExtension['ignored']); @@ -907,7 +903,7 @@ describe('Message test suite', function() { message.getDefaultOneofACase()); message = - new proto.jspb.test.TestMessageWithOneof(new Array(9).concat(567,890)); + new proto.jspb.test.TestMessageWithOneof(new Array(9).concat(567, 890)); assertEquals(1234, message.getAone()); assertEquals(890, message.getAtwo()); assertEquals( @@ -936,7 +932,7 @@ describe('Message test suite', function() { message.getDefaultOneofBCase()); message = new proto.jspb.test.TestMessageWithOneof( - new Array(11).concat(567,890)); + new Array(11).concat(567, 890)); assertUndefined(message.getBone()); assertEquals(890, message.getBtwo()); assertEquals( @@ -989,4 +985,26 @@ describe('Message test suite', function() { assertEquals('y', array[4]); }); + it('testFloatingPointFieldsSupportNan', function() { + var assertNan = function(x) { + assertTrue('Expected ' + x + ' (' + goog.typeOf(x) + ') to be NaN.', + goog.isNumber(x) && isNaN(x)); + }; + + var message = new proto.jspb.test.FloatingPointFields([ + 'NaN', 'NaN', ['NaN', 'NaN'], 'NaN', + 'NaN', 'NaN', ['NaN', 'NaN'], 'NaN' + ]); + assertNan(message.getOptionalFloatField()); + assertNan(message.getRequiredFloatField()); + assertNan(message.getRepeatedFloatFieldList()[0]); + assertNan(message.getRepeatedFloatFieldList()[1]); + assertNan(message.getDefaultFloatField()); + assertNan(message.getOptionalDoubleField()); + assertNan(message.getRequiredDoubleField()); + assertNan(message.getRepeatedDoubleFieldList()[0]); + assertNan(message.getRepeatedDoubleFieldList()[1]); + assertNan(message.getDefaultDoubleField()); + }); + }); diff --git a/js/proto3_test.js b/js/proto3_test.js index f8868716..4dd7790f 100644 --- a/js/proto3_test.js +++ b/js/proto3_test.js @@ -28,6 +28,7 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +goog.require('goog.crypt.base64'); goog.require('goog.testing.asserts'); // CommonJS-LoadFromFile: testbinary_pb proto.jspb.test @@ -37,31 +38,30 @@ goog.require('proto.jspb.test.ForeignMessage'); goog.require('proto.jspb.test.Proto3Enum'); goog.require('proto.jspb.test.TestProto3'); + +var BYTES = new Uint8Array([1, 2, 8, 9]); +var BYTES_B64 = goog.crypt.base64.encodeByteArray(BYTES); + + /** - * Helper: compare a bytes field to a string with codepoints 0--255. + * Helper: compare a bytes field to an expected value * @param {Uint8Array|string} arr - * @param {string} str + * @param {Uint8Array} expected * @return {boolean} */ -function bytesCompare(arr, str) { - if (arr.length != str.length) { +function bytesCompare(arr, expected) { + if (goog.isString(arr)) { + arr = goog.crypt.base64.decodeStringToUint8Array(arr); + } + if (arr.length != expected.length) { return false; } - if (typeof arr == 'string') { - for (var i = 0; i < arr.length; i++) { - if (arr.charCodeAt(i) != str.charCodeAt(i)) { - return false; - } + for (var i = 0; i < arr.length; i++) { + if (arr[i] != expected[i]) { + return false; } - return true; - } else { - for (var i = 0; i < arr.length; i++) { - if (arr[i] != str.charCodeAt(i)) { - return false; - } - } - return true; } + return true; } @@ -86,13 +86,17 @@ describe('proto3Test', function() { assertEquals(msg.getOptionalDouble(), 0); assertEquals(msg.getOptionalString(), ''); - // If/when we change bytes fields to return Uint8Array, we'll want to switch - // to this assertion instead: - //assertEquals(msg.getOptionalBytes() instanceof Uint8Array, true); + // TODO(b/26173701): when we change bytes fields default getter to return + // Uint8Array, we'll want to switch this assertion to match the u8 case. assertEquals(typeof msg.getOptionalBytes(), 'string'); - + assertEquals(msg.getOptionalBytes_asU8() instanceof Uint8Array, true); + assertEquals(typeof msg.getOptionalBytes_asB64(), 'string'); assertEquals(msg.getOptionalBytes().length, 0); - assertEquals(msg.getOptionalForeignEnum(), proto.jspb.test.Proto3Enum.PROTO3_FOO); + assertEquals(msg.getOptionalBytes_asU8().length, 0); + assertEquals(msg.getOptionalBytes_asB64(), ''); + + assertEquals(msg.getOptionalForeignEnum(), + proto.jspb.test.Proto3Enum.PROTO3_FOO); assertEquals(msg.getOptionalForeignMessage(), undefined); assertEquals(msg.getOptionalForeignMessage(), undefined); @@ -136,7 +140,7 @@ describe('proto3Test', function() { msg.setOptionalDouble(-1.5); msg.setOptionalBool(true); msg.setOptionalString('hello world'); - msg.setOptionalBytes('bytes'); + msg.setOptionalBytes(BYTES); var submsg = new proto.jspb.test.ForeignMessage(); submsg.setC(16); msg.setOptionalForeignMessage(submsg); @@ -156,7 +160,7 @@ describe('proto3Test', function() { msg.setRepeatedDoubleList([-1.5]); msg.setRepeatedBoolList([true]); msg.setRepeatedStringList(['hello world']); - msg.setRepeatedBytesList(['bytes']); + msg.setRepeatedBytesList([BYTES]); submsg = new proto.jspb.test.ForeignMessage(); submsg.setC(1000); msg.setRepeatedForeignMessageList([submsg]); @@ -181,7 +185,7 @@ describe('proto3Test', function() { assertEquals(msg.getOptionalDouble(), -1.5); assertEquals(msg.getOptionalBool(), true); assertEquals(msg.getOptionalString(), 'hello world'); - assertEquals(true, bytesCompare(msg.getOptionalBytes(), 'bytes')); + assertEquals(true, bytesCompare(msg.getOptionalBytes(), BYTES)); assertEquals(msg.getOptionalForeignMessage().getC(), 16); assertEquals(msg.getOptionalForeignEnum(), proto.jspb.test.Proto3Enum.PROTO3_BAR); @@ -201,7 +205,7 @@ describe('proto3Test', function() { assertElementsEquals(msg.getRepeatedBoolList(), [true]); assertElementsEquals(msg.getRepeatedStringList(), ['hello world']); assertEquals(msg.getRepeatedBytesList().length, 1); - assertEquals(true, bytesCompare(msg.getRepeatedBytesList()[0], 'bytes')); + assertEquals(true, bytesCompare(msg.getRepeatedBytesList()[0], BYTES)); assertEquals(msg.getRepeatedForeignMessageList().length, 1); assertEquals(msg.getRepeatedForeignMessageList()[0].getC(), 1000); assertElementsEquals(msg.getRepeatedForeignEnumList(), @@ -242,11 +246,12 @@ describe('proto3Test', function() { assertEquals(msg.getOneofString(), 'hello'); assertEquals(msg.getOneofBytes(), undefined); - msg.setOneofBytes('\u00FF\u00FF'); + msg.setOneofBytes(goog.crypt.base64.encodeString('\u00FF\u00FF')); assertEquals(msg.getOneofUint32(), undefined); assertEquals(msg.getOneofForeignMessage(), undefined); assertEquals(msg.getOneofString(), undefined); - assertEquals(msg.getOneofBytes(), '\u00FF\u00FF'); + assertEquals(msg.getOneofBytes_asB64(), + goog.crypt.base64.encodeString('\u00FF\u00FF')); }); @@ -267,7 +272,7 @@ describe('proto3Test', function() { msg.setOptionalBool(false); msg.setOptionalString('hello world'); msg.setOptionalString(''); - msg.setOptionalBytes('\u00FF\u00FF'); + msg.setOptionalBytes(goog.crypt.base64.encodeString('\u00FF\u00FF')); msg.setOptionalBytes(''); msg.setOptionalForeignMessage(new proto.jspb.test.ForeignMessage()); msg.setOptionalForeignMessage(null); @@ -280,4 +285,30 @@ describe('proto3Test', function() { var serialized = msg.serializeBinary(); assertEquals(0, serialized.length); }); + + /** + * Test that base64 string and Uint8Array are interchangeable in bytes fields. + */ + it('testBytesFieldsInterop', function() { + var msg = new proto.jspb.test.TestProto3(); + // Set as a base64 string and check all the getters work. + msg.setOptionalBytes(BYTES_B64); + assertTrue(bytesCompare(msg.getOptionalBytes_asU8(), BYTES)); + assertTrue(bytesCompare(msg.getOptionalBytes_asB64(), BYTES)); + assertTrue(bytesCompare(msg.getOptionalBytes(), BYTES)); + + // Test binary serialize round trip doesn't break it. + msg = proto.jspb.test.TestProto3.deserializeBinary(msg.serializeBinary()); + assertTrue(bytesCompare(msg.getOptionalBytes_asU8(), BYTES)); + assertTrue(bytesCompare(msg.getOptionalBytes_asB64(), BYTES)); + assertTrue(bytesCompare(msg.getOptionalBytes(), BYTES)); + + msg = new proto.jspb.test.TestProto3(); + // Set as a Uint8Array and check all the getters work. + msg.setOptionalBytes(BYTES); + assertTrue(bytesCompare(msg.getOptionalBytes_asU8(), BYTES)); + assertTrue(bytesCompare(msg.getOptionalBytes_asB64(), BYTES)); + assertTrue(bytesCompare(msg.getOptionalBytes(), BYTES)); + + }); }); diff --git a/js/test.proto b/js/test.proto index 14418ac9..6b9dc891 100644 --- a/js/test.proto +++ b/js/test.proto @@ -145,6 +145,17 @@ message DefaultValues { optional bytes bytes_field = 8 [default="moo"]; // Base64 encoding is "bW9v" } +message FloatingPointFields { + optional float optional_float_field = 1; + required float required_float_field = 2; + repeated float repeated_float_field = 3; + optional float default_float_field = 4 [default = 2.0]; + optional double optional_double_field = 5; + required double required_double_field = 6; + repeated double repeated_double_field = 7; + optional double default_double_field = 8 [default = 2.0]; +} + message TestClone { optional string str = 1; optional Simple1 simple1 = 3; @@ -216,3 +227,4 @@ message TestMessageWithOneof { int32 btwo = 13 [default = 1234]; } } + |