Down integrate to GitHub
diff --git a/js/binary/decoder.js b/js/binary/decoder.js index 45cf611..e0cd12e 100644 --- a/js/binary/decoder.js +++ b/js/binary/decoder.js
@@ -44,7 +44,6 @@ */ goog.provide('jspb.BinaryDecoder'); -goog.provide('jspb.BinaryIterator'); goog.require('goog.asserts'); goog.require('goog.crypt'); @@ -53,164 +52,6 @@ /** - * Simple helper class for traversing the contents of repeated scalar fields. - * that may or may not have been packed into a wire-format blob. - * @param {?jspb.BinaryDecoder=} opt_decoder - * @param {?function(this:jspb.BinaryDecoder):(number|boolean|string)=} - * opt_next The decoder method to use for next(). - * @param {?Array<number|boolean|string>=} opt_elements - * @constructor - * @struct - */ -jspb.BinaryIterator = function(opt_decoder, opt_next, opt_elements) { - /** @private {?jspb.BinaryDecoder} */ - this.decoder_ = null; - - /** - * The BinaryDecoder member function used when iterating over packed data. - * @private {?function(this:jspb.BinaryDecoder):(number|boolean|string)} - */ - this.nextMethod_ = null; - - /** @private {?Array<number|boolean|string>} */ - this.elements_ = null; - - /** @private {number} */ - this.cursor_ = 0; - - /** @private {number|boolean|string|null} */ - this.nextValue_ = null; - - /** @private {boolean} */ - this.atEnd_ = true; - - this.init_(opt_decoder, opt_next, opt_elements); -}; - - -/** - * @param {?jspb.BinaryDecoder=} opt_decoder - * @param {?function(this:jspb.BinaryDecoder):(number|boolean|string)=} - * opt_next The decoder method to use for next(). - * @param {?Array<number|boolean|string>=} opt_elements - * @private - */ -jspb.BinaryIterator.prototype.init_ = - function(opt_decoder, opt_next, opt_elements) { - if (opt_decoder && opt_next) { - this.decoder_ = opt_decoder; - this.nextMethod_ = opt_next; - } - this.elements_ = opt_elements || null; - this.cursor_ = 0; - this.nextValue_ = null; - this.atEnd_ = !this.decoder_ && !this.elements_; - - this.next(); -}; - - -/** - * Global pool of BinaryIterator instances. - * @private {!Array<!jspb.BinaryIterator>} - */ -jspb.BinaryIterator.instanceCache_ = []; - - -/** - * Allocates a BinaryIterator from the cache, creating a new one if the cache - * is empty. - * @param {?jspb.BinaryDecoder=} opt_decoder - * @param {?function(this:jspb.BinaryDecoder):(number|boolean|string)=} - * opt_next The decoder method to use for next(). - * @param {?Array<number|boolean|string>=} opt_elements - * @return {!jspb.BinaryIterator} - */ -jspb.BinaryIterator.alloc = function(opt_decoder, opt_next, opt_elements) { - if (jspb.BinaryIterator.instanceCache_.length) { - var iterator = jspb.BinaryIterator.instanceCache_.pop(); - iterator.init_(opt_decoder, opt_next, opt_elements); - return iterator; - } else { - return new jspb.BinaryIterator(opt_decoder, opt_next, opt_elements); - } -}; - - -/** - * Puts this instance back in the instance cache. - */ -jspb.BinaryIterator.prototype.free = function() { - this.clear(); - if (jspb.BinaryIterator.instanceCache_.length < 100) { - jspb.BinaryIterator.instanceCache_.push(this); - } -}; - - -/** - * Clears the iterator. - */ -jspb.BinaryIterator.prototype.clear = function() { - if (this.decoder_) { - this.decoder_.free(); - } - this.decoder_ = null; - this.nextMethod_ = null; - this.elements_ = null; - this.cursor_ = 0; - this.nextValue_ = null; - this.atEnd_ = true; -}; - - -/** - * Returns the element at the iterator, or null if the iterator is invalid or - * past the end of the decoder/array. - * @return {number|boolean|string|null} - */ -jspb.BinaryIterator.prototype.get = function() { - return this.nextValue_; -}; - - -/** - * Returns true if the iterator is at the end of the decoder/array. - * @return {boolean} - */ -jspb.BinaryIterator.prototype.atEnd = function() { - return this.atEnd_; -}; - - -/** - * Returns the element at the iterator and steps to the next element, - * equivalent to '*pointer++' in C. - * @return {number|boolean|string|null} - */ -jspb.BinaryIterator.prototype.next = function() { - var lastValue = this.nextValue_; - if (this.decoder_) { - if (this.decoder_.atEnd()) { - this.nextValue_ = null; - this.atEnd_ = true; - } else { - this.nextValue_ = this.nextMethod_.call(this.decoder_); - } - } else if (this.elements_) { - if (this.cursor_ == this.elements_.length) { - this.nextValue_ = null; - this.atEnd_ = true; - } else { - this.nextValue_ = this.elements_[this.cursor_++]; - } - } - return lastValue; -}; - - - -/** * BinaryDecoder implements the decoders for all the wire types specified in * https://developers.google.com/protocol-buffers/docs/encoding. * @@ -483,6 +324,32 @@ /** + * Reads a signed zigzag encoded varint from the binary stream and invokes + * the conversion function with the value in two signed 32 bit integers to + * produce the result. Since this does not convert the value to a number, no + * precision is lost. + * + * It's possible for an unsigned varint to be incorrectly encoded - more than + * 64 bits' worth of data could be present. If this happens, this method will + * throw an error. + * + * Zigzag encoding is a modification of varint encoding that reduces the + * storage overhead for small negative integers - for more details on the + * format, see https://developers.google.com/protocol-buffers/docs/encoding + * + * @param {function(number, number): T} convert Conversion function to produce + * the result value, takes parameters (lowBits, highBits). + * @return {T} + * @template T + */ +jspb.BinaryDecoder.prototype.readSplitZigzagVarint64 = function(convert) { + return this.readSplitVarint64(function(low, high) { + return jspb.utils.fromZigzag64(low, high, convert); + }); +}; + + +/** * Reads a 64-bit fixed-width value from the stream and invokes the conversion * function with the value in two signed 32 bit integers to produce the result. * Since this does not convert the value to a number, no precision is lost. @@ -732,8 +599,24 @@ /** + * Reads a signed, zigzag-encoded 64-bit varint from the binary stream + * losslessly and returns it as an 8-character Unicode string for use as a hash + * table key. + * + * Zigzag encoding is a modification of varint encoding that reduces the + * storage overhead for small negative integers - for more details on the + * format, see https://developers.google.com/protocol-buffers/docs/encoding + * + * @return {string} The decoded zigzag varint in hash64 format. + */ +jspb.BinaryDecoder.prototype.readZigzagVarintHash64 = function() { + return this.readSplitZigzagVarint64(jspb.utils.joinHash64); +}; + + +/** * Reads a signed, zigzag-encoded 64-bit varint from the binary stream and - * returns its valud as a string. + * returns its value as a string. * * Zigzag encoding is a modification of varint encoding that reduces the * storage overhead for small negative integers - for more details on the @@ -743,9 +626,7 @@ * string. */ jspb.BinaryDecoder.prototype.readZigzagVarint64String = function() { - // TODO(haberman): write lossless 64-bit zig-zag math. - var value = this.readZigzagVarint64(); - return value.toString(); + return this.readSplitZigzagVarint64(jspb.utils.joinSignedDecimalString); };
diff --git a/js/binary/decoder_test.js b/js/binary/decoder_test.js index c5be805..393f2f7 100644 --- a/js/binary/decoder_test.js +++ b/js/binary/decoder_test.js
@@ -235,6 +235,95 @@ }); }); + describe('sint64', function() { + var /** !jspb.BinaryDecoder */ decoder; + + var hashA = + String.fromCharCode(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); + var hashB = + String.fromCharCode(0x12, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); + var hashC = + String.fromCharCode(0x12, 0x34, 0x56, 0x78, 0x87, 0x65, 0x43, 0x21); + var hashD = + String.fromCharCode(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF); + beforeEach(function() { + var encoder = new jspb.BinaryEncoder(); + + encoder.writeZigzagVarintHash64(hashA); + encoder.writeZigzagVarintHash64(hashB); + encoder.writeZigzagVarintHash64(hashC); + encoder.writeZigzagVarintHash64(hashD); + + decoder = jspb.BinaryDecoder.alloc(encoder.end()); + }); + + it('reads 64-bit integers as decimal strings', function() { + const signed = true; + expect(decoder.readZigzagVarint64String()) + .toEqual(jspb.utils.hash64ToDecimalString(hashA, signed)); + expect(decoder.readZigzagVarint64String()) + .toEqual(jspb.utils.hash64ToDecimalString(hashB, signed)); + expect(decoder.readZigzagVarint64String()) + .toEqual(jspb.utils.hash64ToDecimalString(hashC, signed)); + expect(decoder.readZigzagVarint64String()) + .toEqual(jspb.utils.hash64ToDecimalString(hashD, signed)); + }); + + it('reads 64-bit integers as hash strings', function() { + expect(decoder.readZigzagVarintHash64()).toEqual(hashA); + expect(decoder.readZigzagVarintHash64()).toEqual(hashB); + expect(decoder.readZigzagVarintHash64()).toEqual(hashC); + expect(decoder.readZigzagVarintHash64()).toEqual(hashD); + }); + + it('reads split 64 bit zigzag integers', function() { + function hexJoin(bitsLow, bitsHigh) { + return `0x${(bitsHigh >>> 0).toString(16)}:0x${ + (bitsLow >>> 0).toString(16)}`; + } + function hexJoinHash(hash64) { + jspb.utils.splitHash64(hash64); + return hexJoin(jspb.utils.split64Low, jspb.utils.split64High); + } + + expect(decoder.readSplitZigzagVarint64(hexJoin)) + .toEqual(hexJoinHash(hashA)); + expect(decoder.readSplitZigzagVarint64(hexJoin)) + .toEqual(hexJoinHash(hashB)); + expect(decoder.readSplitZigzagVarint64(hexJoin)) + .toEqual(hexJoinHash(hashC)); + expect(decoder.readSplitZigzagVarint64(hexJoin)) + .toEqual(hexJoinHash(hashD)); + }); + + it('does zigzag encoding properly', function() { + // Test cases direcly from the protobuf dev guide. + // https://engdoc.corp.google.com/eng/howto/protocolbuffers/developerguide/encoding.shtml?cl=head#types + var testCases = [ + {original: '0', zigzag: '0'}, + {original: '-1', zigzag: '1'}, + {original: '1', zigzag: '2'}, + {original: '-2', zigzag: '3'}, + {original: '2147483647', zigzag: '4294967294'}, + {original: '-2147483648', zigzag: '4294967295'}, + // 64-bit extremes, not in dev guide. + {original: '9223372036854775807', zigzag: '18446744073709551614'}, + {original: '-9223372036854775808', zigzag: '18446744073709551615'}, + ]; + var encoder = new jspb.BinaryEncoder(); + testCases.forEach(function(c) { + encoder.writeZigzagVarint64String(c.original); + }); + var buffer = encoder.end(); + var zigzagDecoder = jspb.BinaryDecoder.alloc(buffer); + var varintDecoder = jspb.BinaryDecoder.alloc(buffer); + testCases.forEach(function(c) { + expect(zigzagDecoder.readZigzagVarint64String()).toEqual(c.original); + expect(varintDecoder.readUnsignedVarint64String()).toEqual(c.zigzag); + }); + }); + }); + /** * Tests reading and writing large strings */
diff --git a/js/binary/encoder.js b/js/binary/encoder.js index b2013f6..0b48e05 100644 --- a/js/binary/encoder.js +++ b/js/binary/encoder.js
@@ -232,13 +232,27 @@ * @param {string} value The integer to convert. */ jspb.BinaryEncoder.prototype.writeZigzagVarint64String = function(value) { - // TODO(haberman): write lossless 64-bit zig-zag math. - this.writeZigzagVarint64(parseInt(value, 10)); + this.writeZigzagVarintHash64(jspb.utils.decimalStringToHash64(value)); }; /** - * Writes a 8-bit unsigned integer to the buffer. Numbers outside the range + * Writes a 64-bit hash string (8 characters @ 8 bits of data each) to the + * buffer as a zigzag varint. + * @param {string} hash The hash to write. + */ +jspb.BinaryEncoder.prototype.writeZigzagVarintHash64 = function(hash) { + var self = this; + jspb.utils.splitHash64(hash); + jspb.utils.toZigzag64( + jspb.utils.split64Low, jspb.utils.split64High, function(lo, hi) { + self.writeSplitVarint64(lo >>> 0, hi >>> 0); + }); +}; + + +/** + * Writes an 8-bit unsigned integer to the buffer. Numbers outside the range * [0,2^8) will be truncated. * @param {number} value The value to write. */ @@ -294,7 +308,7 @@ /** - * Writes a 8-bit integer to the buffer. Numbers outside the range + * Writes an 8-bit integer to the buffer. Numbers outside the range * [-2^7,2^7) will be truncated. * @param {number} value The value to write. */
diff --git a/js/binary/reader.js b/js/binary/reader.js index d1ab407..9e193ac 100644 --- a/js/binary/reader.js +++ b/js/binary/reader.js
@@ -49,6 +49,7 @@ goog.require('goog.asserts'); goog.require('jspb.BinaryConstants'); goog.require('jspb.BinaryDecoder'); +goog.require('jspb.utils'); @@ -941,7 +942,7 @@ /** - * Reads a 64-bit varint or fixed64 field from the stream and returns it as a + * Reads a 64-bit varint or fixed64 field from the stream and returns it as an * 8-character Unicode string for use as a hash table key, or throws an error * if the next field in the stream is not of the correct wire type. * @@ -955,6 +956,20 @@ /** + * Reads an sint64 field from the stream and returns it as an 8-character + * Unicode string for use as a hash table key, or throws an error if the next + * field in the stream is not of the correct wire type. + * + * @return {string} The hash value. + */ +jspb.BinaryReader.prototype.readSintHash64 = function() { + goog.asserts.assert( + this.nextWireType_ == jspb.BinaryConstants.WireType.VARINT); + return this.decoder_.readZigzagVarintHash64(); +}; + + +/** * Reads a 64-bit varint field from the stream and invokes `convert` to produce * the return value, or throws an error if the next field in the stream is not * of the correct wire type. @@ -972,6 +987,25 @@ /** + * Reads a 64-bit zig-zag varint field from the stream and invokes `convert` to + * produce the return value, or throws an error if the next field in the stream + * is not of the correct wire type. + * + * @param {function(number, number): T} convert Conversion function to produce + * the result value, takes parameters (lowBits, highBits). + * @return {T} + * @template T + */ +jspb.BinaryReader.prototype.readSplitZigzagVarint64 = function(convert) { + goog.asserts.assert( + this.nextWireType_ == jspb.BinaryConstants.WireType.VARINT); + return this.decoder_.readSplitVarint64(function(lowBits, highBits) { + return jspb.utils.fromZigzag64(lowBits, highBits, convert); + }); +}; + + +/** * Reads a 64-bit varint or fixed64 field from the stream and returns it as a * 8-character Unicode string for use as a hash table key, or throws an error * if the next field in the stream is not of the correct wire type.
diff --git a/js/binary/reader_test.js b/js/binary/reader_test.js index 618e9ad..daa0ab6 100644 --- a/js/binary/reader_test.js +++ b/js/binary/reader_test.js
@@ -414,6 +414,7 @@ var writer = new jspb.BinaryWriter(); writer.writeInt64String(1, '4294967296'); writer.writeSfixed64String(2, '4294967298'); + writer.writeInt64String(3, '3'); // 3 is the zig-zag encoding of -2. var reader = jspb.BinaryReader.alloc(writer.getResultBuffer()); function rejoin(lowBits, highBits) { @@ -426,6 +427,10 @@ reader.nextField(); expect(reader.getFieldNumber()).toEqual(2); expect(reader.readSplitFixed64(rejoin)).toEqual(0x100000002); + + reader.nextField(); + expect(reader.getFieldNumber()).toEqual(3); + expect(reader.readSplitZigzagVarint64(rejoin)).toEqual(-2); }); /** @@ -490,6 +495,11 @@ jspb.BinaryReader.prototype.readSint64, jspb.BinaryWriter.prototype.writeSint64, 1, -Math.pow(2, 63), Math.pow(2, 63) - 513, Math.round); + + doTestSignedField_( + jspb.BinaryReader.prototype.readSintHash64, + jspb.BinaryWriter.prototype.writeSintHash64, 1, -Math.pow(2, 63), + Math.pow(2, 63) - 513, jspb.utils.numberToHash64); });
diff --git a/js/binary/utils.js b/js/binary/utils.js index 0cf0ef0..f0425e3 100644 --- a/js/binary/utils.js +++ b/js/binary/utils.js
@@ -296,7 +296,7 @@ * @return {number} */ jspb.utils.joinUint64 = function(bitsLow, bitsHigh) { - return bitsHigh * jspb.BinaryConstants.TWO_TO_32 + bitsLow; + return bitsHigh * jspb.BinaryConstants.TWO_TO_32 + (bitsLow >>> 0); }; @@ -322,6 +322,33 @@ return sign ? -result : result; }; +/** + * Converts split 64-bit values from standard two's complement encoding to + * zig-zag encoding. Invokes the provided function to produce final result. + * + * @param {number} bitsLow + * @param {number} bitsHigh + * @param {function(number, number): T} convert Conversion function to produce + * the result value, takes parameters (lowBits, highBits). + * @return {T} + * @template T + */ +jspb.utils.toZigzag64 = function(bitsLow, bitsHigh, convert) { + // See + // https://engdoc.corp.google.com/eng/howto/protocolbuffers/developerguide/encoding.shtml?cl=head#types + // 64-bit math is: (n << 1) ^ (n >> 63) + // + // To do this in 32 bits, we can get a 32-bit sign-flipping mask from the + // high word. + // Then we can operate on each word individually, with the addition of the + // "carry" to get the most significant bit from the low word into the high + // word. + var signFlipMask = bitsHigh >> 31; + bitsHigh = (bitsHigh << 1 | bitsLow >>> 31) ^ signFlipMask; + bitsLow = (bitsLow << 1) ^ signFlipMask; + return convert(bitsLow, bitsHigh); +}; + /** * Joins two 32-bit values into a 64-bit unsigned integer and applies zigzag @@ -331,21 +358,33 @@ * @return {number} */ jspb.utils.joinZigzag64 = function(bitsLow, bitsHigh) { - // Extract the sign bit and shift right by one. - var sign = bitsLow & 1; - bitsLow = ((bitsLow >>> 1) | (bitsHigh << 31)) >>> 0; - bitsHigh = bitsHigh >>> 1; + return jspb.utils.fromZigzag64(bitsLow, bitsHigh, jspb.utils.joinInt64); +}; - // Increment the split value if the sign bit was set. - if (sign) { - bitsLow = (bitsLow + 1) >>> 0; - if (bitsLow == 0) { - bitsHigh = (bitsHigh + 1) >>> 0; - } - } - var result = jspb.utils.joinUint64(bitsLow, bitsHigh); - return sign ? -result : result; +/** + * Converts split 64-bit values from zigzag encoding to standard two's + * complement encoding. Invokes the provided function to produce final result. + * + * @param {number} bitsLow + * @param {number} bitsHigh + * @param {function(number, number): T} convert Conversion function to produce + * the result value, takes parameters (lowBits, highBits). + * @return {T} + * @template T + */ +jspb.utils.fromZigzag64 = function(bitsLow, bitsHigh, convert) { + // 64 bit math is: + // signmask = (zigzag & 1) ? -1 : 0; + // twosComplement = (zigzag >> 1) ^ signmask; + // + // To work with 32 bit, we can operate on both but "carry" the lowest bit + // from the high word by shifting it up 31 bits to be the most significant bit + // of the low word. + var signFlipMask = -(bitsLow & 1); + bitsLow = ((bitsLow >>> 1) | (bitsHigh << 31)) ^ signFlipMask; + bitsHigh = (bitsHigh >>> 1) ^ signFlipMask; + return convert(bitsLow, bitsHigh); };
diff --git a/js/binary/utils_test.js b/js/binary/utils_test.js index d4c1ef7..c1b0789 100644 --- a/js/binary/utils_test.js +++ b/js/binary/utils_test.js
@@ -465,6 +465,53 @@ } }); + /** + * Tests zigzag conversions. + */ + it('can encode and decode zigzag 64', function() { + function stringToHiLoPair(str) { + jspb.utils.splitDecimalString(str); + return { + lo: jspb.utils.split64Low >>> 0, + hi: jspb.utils.split64High >>> 0 + }; + } + function makeHiLoPair(lo, hi) { + return {lo: lo >>> 0, hi: hi >>> 0}; + } + // Test cases direcly from the protobuf dev guide. + // https://engdoc.corp.google.com/eng/howto/protocolbuffers/developerguide/encoding.shtml?cl=head#types + var testCases = [ + {original: stringToHiLoPair('0'), zigzag: stringToHiLoPair('0')}, + {original: stringToHiLoPair('-1'), zigzag: stringToHiLoPair('1')}, + {original: stringToHiLoPair('1'), zigzag: stringToHiLoPair('2')}, + {original: stringToHiLoPair('-2'), zigzag: stringToHiLoPair('3')}, + { + original: stringToHiLoPair('2147483647'), + zigzag: stringToHiLoPair('4294967294') + }, + { + original: stringToHiLoPair('-2147483648'), + zigzag: stringToHiLoPair('4294967295') + }, + // 64-bit extremes + { + original: stringToHiLoPair('9223372036854775807'), + zigzag: stringToHiLoPair('18446744073709551614') + }, + { + original: stringToHiLoPair('-9223372036854775808'), + zigzag: stringToHiLoPair('18446744073709551615') + }, + ]; + for (const c of testCases) { + expect(jspb.utils.toZigzag64(c.original.lo, c.original.hi, makeHiLoPair)) + .toEqual(c.zigzag); + expect(jspb.utils.fromZigzag64(c.zigzag.lo, c.zigzag.hi, makeHiLoPair)) + .toEqual(c.original); + } + }); + /** * Tests counting packed varints.
diff --git a/js/binary/writer.js b/js/binary/writer.js index 017d481..8c5bff8 100644 --- a/js/binary/writer.js +++ b/js/binary/writer.js
@@ -236,12 +236,11 @@ /** * Converts the encoded data into a base64-encoded string. - * @param {boolean=} opt_webSafe True indicates we should use a websafe - * alphabet, which does not require escaping for use in URLs. + * @param {!goog.crypt.base64.Alphabet=} alphabet Which flavor of base64 to use. * @return {string} */ -jspb.BinaryWriter.prototype.getResultBase64String = function(opt_webSafe) { - return goog.crypt.base64.encodeByteArray(this.getResultBuffer(), opt_webSafe); +jspb.BinaryWriter.prototype.getResultBase64String = function(alphabet) { + return goog.crypt.base64.encodeByteArray(this.getResultBuffer(), alphabet); }; @@ -451,6 +450,19 @@ /** + * Writes a zigzag varint field to the buffer without range checking. + * @param {number} field The field number. + * @param {string?} value The value to write. + * @private + */ +jspb.BinaryWriter.prototype.writeZigzagVarintHash64_ = function(field, value) { + if (value == null) return; + this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.VARINT); + this.encoder_.writeZigzagVarintHash64(value); +}; + + +/** * Writes an int32 field to the buffer. Numbers outside the range [-2^31,2^31) * will be truncated. * @param {number} field The field number. @@ -563,7 +575,7 @@ /** - * Writes a sint32 field to the buffer. Numbers outside the range [-2^31,2^31) + * Writes an sint32 field to the buffer. Numbers outside the range [-2^31,2^31) * will be truncated. * @param {number} field The field number. * @param {number?} value The value to write. @@ -577,7 +589,7 @@ /** - * Writes a sint64 field to the buffer. Numbers outside the range [-2^63,2^63) + * Writes an sint64 field to the buffer. Numbers outside the range [-2^63,2^63) * will be truncated. * @param {number} field The field number. * @param {number?} value The value to write. @@ -591,15 +603,25 @@ /** - * Writes a sint64 field to the buffer. Numbers outside the range [-2^63,2^63) + * Writes an sint64 field to the buffer from a hash64 encoded value. Numbers + * outside the range [-2^63,2^63) will be truncated. + * @param {number} field The field number. + * @param {string?} value The hash64 string to write. + */ +jspb.BinaryWriter.prototype.writeSintHash64 = function(field, value) { + if (value == null) return; + this.writeZigzagVarintHash64_(field, value); +}; + + +/** + * Writes an sint64 field to the buffer. Numbers outside the range [-2^63,2^63) * will be truncated. * @param {number} field The field number. * @param {string?} value The decimal string to write. */ jspb.BinaryWriter.prototype.writeSint64String = function(field, value) { if (value == null) return; - goog.asserts.assert((+value >= -jspb.BinaryConstants.TWO_TO_63) && - (+value < jspb.BinaryConstants.TWO_TO_63)); this.writeZigzagVarint64String_(field, value); }; @@ -913,6 +935,22 @@ /** + * Writes a 64-bit field to the buffer as a zigzag encoded varint. + * @param {number} field The field number. + * @param {number} lowBits The low 32 bits. + * @param {number} highBits The high 32 bits. + */ +jspb.BinaryWriter.prototype.writeSplitZigzagVarint64 = function( + field, lowBits, highBits) { + this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.VARINT); + var encoder = this.encoder_; + jspb.utils.toZigzag64(lowBits, highBits, function(lowBits, highBits) { + encoder.writeSplitVarint64(lowBits >>> 0, highBits >>> 0); + }); +}; + + +/** * Writes an array of numbers to the buffer as a repeated 32-bit int field. * @param {number} field The field number. * @param {?Array<number>} value The array of ints to write. @@ -987,6 +1025,23 @@ /** + * Writes an array of 64-bit values to the buffer as a zigzag varint. + * @param {number} field The field number. + * @param {?Array<T>} value The value. + * @param {function(T): number} lo Function to get low bits. + * @param {function(T): number} hi Function to get high bits. + * @template T + */ +jspb.BinaryWriter.prototype.writeRepeatedSplitZigzagVarint64 = function( + field, value, lo, hi) { + if (value == null) return; + for (var i = 0; i < value.length; i++) { + this.writeSplitZigzagVarint64(field, lo(value[i]), hi(value[i])); + } +}; + + +/** * Writes an array of numbers formatted as strings to the buffer as a repeated * 64-bit int field. * @param {number} field The field number. @@ -1096,6 +1151,20 @@ /** + * Writes an array of hash64 strings to the buffer as a repeated signed 64-bit + * int field. + * @param {number} field The field number. + * @param {?Array<string>} value The array of ints to write. + */ +jspb.BinaryWriter.prototype.writeRepeatedSintHash64 = function(field, value) { + if (value == null) return; + for (var i = 0; i < value.length; i++) { + this.writeZigzagVarintHash64_(field, value[i]); + } +}; + + +/** * Writes an array of numbers to the buffer as a repeated fixed32 field. This * works for both signed and unsigned fixed32s. * @param {number} field The field number. @@ -1412,6 +1481,29 @@ /** + * Writes an array of 64-bit values to the buffer as a zigzag varint. + * @param {number} field The field number. + * @param {?Array<T>} value The value. + * @param {function(T): number} lo Function to get low bits. + * @param {function(T): number} hi Function to get high bits. + * @template T + */ +jspb.BinaryWriter.prototype.writePackedSplitZigzagVarint64 = function( + field, value, lo, hi) { + if (value == null) return; + var bookmark = this.beginDelimited_(field); + var encoder = this.encoder_; + for (var i = 0; i < value.length; i++) { + jspb.utils.toZigzag64( + lo(value[i]), hi(value[i]), function(bitsLow, bitsHigh) { + encoder.writeSplitVarint64(bitsLow >>> 0, bitsHigh >>> 0); + }); + } + this.endDelimited_(bookmark); +}; + + +/** * Writes an array of numbers represented as strings to the buffer as a packed * 64-bit int field. * @param {number} field @@ -1533,8 +1625,24 @@ if (value == null || !value.length) return; var bookmark = this.beginDelimited_(field); for (var i = 0; i < value.length; i++) { - // TODO(haberman): make lossless - this.encoder_.writeZigzagVarint64(parseInt(value[i], 10)); + this.encoder_.writeZigzagVarintHash64( + jspb.utils.decimalStringToHash64(value[i])); + } + this.endDelimited_(bookmark); +}; + + +/** + * Writes an array of hash 64 strings to the buffer as a packed signed 64-bit + * int field. + * @param {number} field The field number. + * @param {?Array<string>} value The array of decimal strings to write. + */ +jspb.BinaryWriter.prototype.writePackedSintHash64 = function(field, value) { + if (value == null || !value.length) return; + var bookmark = this.beginDelimited_(field); + for (var i = 0; i < value.length; i++) { + this.encoder_.writeZigzagVarintHash64(value[i]); } this.endDelimited_(bookmark); };
diff --git a/js/binary/writer_test.js b/js/binary/writer_test.js index b4860a1..0590464 100644 --- a/js/binary/writer_test.js +++ b/js/binary/writer_test.js
@@ -42,6 +42,7 @@ goog.require('goog.testing.asserts'); goog.require('jspb.BinaryReader'); goog.require('jspb.BinaryWriter'); +goog.require('jspb.utils'); /** @@ -128,8 +129,13 @@ var writer = new jspb.BinaryWriter(); writer.writeBytes(1, new Uint8Array([127])); assertEquals('CgF/', writer.getResultBase64String()); - assertEquals('CgF/', writer.getResultBase64String(false)); - assertEquals('CgF_', writer.getResultBase64String(true)); + assertEquals( + 'CgF/', + writer.getResultBase64String(goog.crypt.base64.Alphabet.DEFAULT)); + assertEquals( + 'CgF_', + writer.getResultBase64String( + goog.crypt.base64.Alphabet.WEBSAFE_NO_PADDING)); }); it('writes split 64 fields', function() { @@ -201,4 +207,116 @@ String(4 * 2 ** 32 + 3), ]); }); + + it('writes zigzag 64 fields', function() { + // Test cases direcly from the protobuf dev guide. + // https://engdoc.corp.google.com/eng/howto/protocolbuffers/developerguide/encoding.shtml?cl=head#types + var testCases = [ + {original: '0', zigzag: '0'}, + {original: '-1', zigzag: '1'}, + {original: '1', zigzag: '2'}, + {original: '-2', zigzag: '3'}, + {original: '2147483647', zigzag: '4294967294'}, + {original: '-2147483648', zigzag: '4294967295'}, + // 64-bit extremes, not in dev guide. + {original: '9223372036854775807', zigzag: '18446744073709551614'}, + {original: '-9223372036854775808', zigzag: '18446744073709551615'}, + ]; + function decimalToLowBits(v) { + jspb.utils.splitDecimalString(v); + return jspb.utils.split64Low >>> 0; + } + function decimalToHighBits(v) { + jspb.utils.splitDecimalString(v); + return jspb.utils.split64High >>> 0; + } + + var writer = new jspb.BinaryWriter(); + testCases.forEach(function(c) { + writer.writeSint64String(1, c.original); + writer.writeSintHash64(1, jspb.utils.decimalStringToHash64(c.original)); + jspb.utils.splitDecimalString(c.original); + writer.writeSplitZigzagVarint64( + 1, jspb.utils.split64Low, jspb.utils.split64High); + }); + + writer.writeRepeatedSint64String(2, testCases.map(function(c) { + return c.original; + })); + + writer.writeRepeatedSintHash64(3, testCases.map(function(c) { + return jspb.utils.decimalStringToHash64(c.original); + })); + + writer.writeRepeatedSplitZigzagVarint64( + 4, testCases.map(function(c) { + return c.original; + }), + decimalToLowBits, decimalToHighBits); + + writer.writePackedSint64String(5, testCases.map(function(c) { + return c.original; + })); + + writer.writePackedSintHash64(6, testCases.map(function(c) { + return jspb.utils.decimalStringToHash64(c.original); + })); + + writer.writePackedSplitZigzagVarint64( + 7, testCases.map(function(c) { + return c.original; + }), + decimalToLowBits, decimalToHighBits); + + // Verify by reading the stream as normal int64 fields and checking with + // the canonical zigzag encoding of each value. + var reader = jspb.BinaryReader.alloc(writer.getResultBuffer()); + testCases.forEach(function(c) { + reader.nextField(); + expect(reader.getFieldNumber()).toEqual(1); + expect(reader.readUint64String()).toEqual(c.zigzag); + reader.nextField(); + expect(reader.getFieldNumber()).toEqual(1); + expect(reader.readUint64String()).toEqual(c.zigzag); + reader.nextField(); + expect(reader.getFieldNumber()).toEqual(1); + expect(reader.readUint64String()).toEqual(c.zigzag); + }); + + testCases.forEach(function(c) { + reader.nextField(); + expect(reader.getFieldNumber()).toEqual(2); + expect(reader.readUint64String()).toEqual(c.zigzag); + }); + + testCases.forEach(function(c) { + reader.nextField(); + expect(reader.getFieldNumber()).toEqual(3); + expect(reader.readUint64String()).toEqual(c.zigzag); + }); + + testCases.forEach(function(c) { + reader.nextField(); + expect(reader.getFieldNumber()).toEqual(4); + expect(reader.readUint64String()).toEqual(c.zigzag); + }); + + reader.nextField(); + expect(reader.getFieldNumber()).toEqual(5); + expect(reader.readPackedUint64String()).toEqual(testCases.map(function(c) { + return c.zigzag; + })); + + reader.nextField(); + expect(reader.getFieldNumber()).toEqual(6); + expect(reader.readPackedUint64String()).toEqual(testCases.map(function(c) { + return c.zigzag; + })); + + reader.nextField(); + expect(reader.getFieldNumber()).toEqual(7); + expect(reader.readPackedUint64String()).toEqual(testCases.map(function(c) { + return c.zigzag; + })); + }); });
diff --git a/js/compatibility_tests/v3.0.0/binary/proto_test.js b/js/compatibility_tests/v3.0.0/binary/proto_test.js index 14d0f42..1364834 100644 --- a/js/compatibility_tests/v3.0.0/binary/proto_test.js +++ b/js/compatibility_tests/v3.0.0/binary/proto_test.js
@@ -172,7 +172,7 @@ * @return {boolean} */ function bytesCompare(arr, expected) { - if (goog.isString(arr)) { + if (typeof arr === 'string') { arr = goog.crypt.base64.decodeStringToUint8Array(arr); } if (arr.length != expected.length) { @@ -477,8 +477,8 @@ var msg = new proto.jspb.test.TestAllTypes(); function assertGetters() { - assertTrue(goog.isString(msg.getRepeatedBytesList_asB64()[0])); - assertTrue(goog.isString(msg.getRepeatedBytesList_asB64()[1])); + assertTrue(typeof msg.getRepeatedBytesList_asB64()[0] === 'string'); + assertTrue(typeof msg.getRepeatedBytesList_asB64()[1] === 'string'); assertTrue(msg.getRepeatedBytesList_asU8()[0] instanceof Uint8Array); assertTrue(msg.getRepeatedBytesList_asU8()[1] instanceof Uint8Array);
diff --git a/js/compatibility_tests/v3.0.0/binary/utils_test.js b/js/compatibility_tests/v3.0.0/binary/utils_test.js index d27e5ea..abc36aa 100644 --- a/js/compatibility_tests/v3.0.0/binary/utils_test.js +++ b/js/compatibility_tests/v3.0.0/binary/utils_test.js
@@ -355,7 +355,7 @@ */ function test(x, opt_bits) { jspb.utils.splitFloat32(x); - if (goog.isDef(opt_bits)) { + if (opt_bits !== undefined) { if (opt_bits != jspb.utils.split64Low) throw 'fail!'; } if (truncate(x) != jspb.utils.joinFloat32(jspb.utils.split64Low, @@ -422,10 +422,10 @@ */ function test(x, opt_highBits, opt_lowBits) { jspb.utils.splitFloat64(x); - if (goog.isDef(opt_highBits)) { + if (opt_highBits !== undefined) { if (opt_highBits != jspb.utils.split64High) throw 'fail!'; } - if (goog.isDef(opt_lowBits)) { + if (opt_lowBits !== undefined) { if (opt_lowBits != jspb.utils.split64Low) throw 'fail!'; } if (x != jspb.utils.joinFloat64(jspb.utils.split64Low,
diff --git a/js/compatibility_tests/v3.0.0/message_test.js b/js/compatibility_tests/v3.0.0/message_test.js index b779143..79a12e0 100644 --- a/js/compatibility_tests/v3.0.0/message_test.js +++ b/js/compatibility_tests/v3.0.0/message_test.js
@@ -1057,8 +1057,9 @@ it('testFloatingPointFieldsSupportNan', function() { var assertNan = function(x) { - assertTrue('Expected ' + x + ' (' + goog.typeOf(x) + ') to be NaN.', - goog.isNumber(x) && isNaN(x)); + assertTrue( + 'Expected ' + x + ' (' + goog.typeOf(x) + ') to be NaN.', + typeof x === 'number' && isNaN(x)); }; var message = new proto.jspb.test.FloatingPointFields([
diff --git a/js/compatibility_tests/v3.0.0/proto3_test.js b/js/compatibility_tests/v3.0.0/proto3_test.js index fab0fd4..d020a11 100644 --- a/js/compatibility_tests/v3.0.0/proto3_test.js +++ b/js/compatibility_tests/v3.0.0/proto3_test.js
@@ -50,7 +50,7 @@ * @return {boolean} */ function bytesCompare(arr, expected) { - if (goog.isString(arr)) { + if (typeof arr === 'string') { arr = goog.crypt.base64.decodeStringToUint8Array(arr); } if (arr.length != expected.length) {
diff --git a/js/compatibility_tests/v3.1.0/binary/proto_test.js b/js/compatibility_tests/v3.1.0/binary/proto_test.js index 26e1d30..ff9d972 100644 --- a/js/compatibility_tests/v3.1.0/binary/proto_test.js +++ b/js/compatibility_tests/v3.1.0/binary/proto_test.js
@@ -172,7 +172,7 @@ * @return {boolean} */ function bytesCompare(arr, expected) { - if (goog.isString(arr)) { + if (typeof arr === 'string') { arr = goog.crypt.base64.decodeStringToUint8Array(arr); } if (arr.length != expected.length) { @@ -477,8 +477,8 @@ var msg = new proto.jspb.test.TestAllTypes(); function assertGetters() { - assertTrue(goog.isString(msg.getRepeatedBytesList_asB64()[0])); - assertTrue(goog.isString(msg.getRepeatedBytesList_asB64()[1])); + assertTrue(typeof msg.getRepeatedBytesList_asB64()[0] === 'string'); + assertTrue(typeof msg.getRepeatedBytesList_asB64()[1] === 'string'); assertTrue(msg.getRepeatedBytesList_asU8()[0] instanceof Uint8Array); assertTrue(msg.getRepeatedBytesList_asU8()[1] instanceof Uint8Array);
diff --git a/js/compatibility_tests/v3.1.0/binary/utils_test.js b/js/compatibility_tests/v3.1.0/binary/utils_test.js index d27e5ea..abc36aa 100644 --- a/js/compatibility_tests/v3.1.0/binary/utils_test.js +++ b/js/compatibility_tests/v3.1.0/binary/utils_test.js
@@ -355,7 +355,7 @@ */ function test(x, opt_bits) { jspb.utils.splitFloat32(x); - if (goog.isDef(opt_bits)) { + if (opt_bits !== undefined) { if (opt_bits != jspb.utils.split64Low) throw 'fail!'; } if (truncate(x) != jspb.utils.joinFloat32(jspb.utils.split64Low, @@ -422,10 +422,10 @@ */ function test(x, opt_highBits, opt_lowBits) { jspb.utils.splitFloat64(x); - if (goog.isDef(opt_highBits)) { + if (opt_highBits !== undefined) { if (opt_highBits != jspb.utils.split64High) throw 'fail!'; } - if (goog.isDef(opt_lowBits)) { + if (opt_lowBits !== undefined) { if (opt_lowBits != jspb.utils.split64Low) throw 'fail!'; } if (x != jspb.utils.joinFloat64(jspb.utils.split64Low,
diff --git a/js/compatibility_tests/v3.1.0/message_test.js b/js/compatibility_tests/v3.1.0/message_test.js index d5c7374..80a1c52 100644 --- a/js/compatibility_tests/v3.1.0/message_test.js +++ b/js/compatibility_tests/v3.1.0/message_test.js
@@ -1009,8 +1009,9 @@ it('testFloatingPointFieldsSupportNan', function() { var assertNan = function(x) { - assertTrue('Expected ' + x + ' (' + goog.typeOf(x) + ') to be NaN.', - goog.isNumber(x) && isNaN(x)); + assertTrue( + 'Expected ' + x + ' (' + goog.typeOf(x) + ') to be NaN.', + typeof x === 'number' && isNaN(x)); }; var message = new proto.jspb.test.FloatingPointFields([
diff --git a/js/compatibility_tests/v3.1.0/proto3_test.js b/js/compatibility_tests/v3.1.0/proto3_test.js index 3c929ef..696af33 100644 --- a/js/compatibility_tests/v3.1.0/proto3_test.js +++ b/js/compatibility_tests/v3.1.0/proto3_test.js
@@ -50,7 +50,7 @@ * @return {boolean} */ function bytesCompare(arr, expected) { - if (goog.isString(arr)) { + if (typeof arr === 'string') { arr = goog.crypt.base64.decodeStringToUint8Array(arr); } if (arr.length != expected.length) {
diff --git a/js/map.js b/js/map.js index b9a48af..589a293 100644 --- a/js/map.js +++ b/js/map.js
@@ -461,15 +461,21 @@ * The BinaryReader parsing callback for type V, if V is a message type * * @param {K=} opt_defaultKey - * The default value for the type of map keys. Accepting map - * entries with unset keys is required for maps to be backwards compatible - * with the repeated message representation described here: goo.gl/zuoLAC + * The default value for the type of map keys. Accepting map entries with + * unset keys is required for maps to be backwards compatible with the + * repeated message representation described here: goo.gl/zuoLAC + * + * @param {V=} opt_defaultValue + * The default value for the type of map values. Accepting map entries with + * unset values is required for maps to be backwards compatible with the + * repeated message representation described here: goo.gl/zuoLAC * */ jspb.Map.deserializeBinary = function(map, reader, keyReaderFn, valueReaderFn, - opt_valueReaderCallback, opt_defaultKey) { + opt_valueReaderCallback, opt_defaultKey, + opt_defaultValue) { var key = opt_defaultKey; - var value = undefined; + var value = opt_defaultValue; while (reader.nextField()) { if (reader.isEndGroup()) { @@ -484,7 +490,11 @@ // Value. if (map.valueCtor_) { goog.asserts.assert(opt_valueReaderCallback); - value = new map.valueCtor_(); + if (!value) { + // Old generator still doesn't provide default value message. + // Need this for backward compatibility. + value = new map.valueCtor_(); + } valueReaderFn.call(reader, value, opt_valueReaderCallback); } else { value =
diff --git a/js/maps_test.js b/js/maps_test.js index 4640c98..1cbff7b 100755 --- a/js/maps_test.js +++ b/js/maps_test.js
@@ -36,10 +36,18 @@ goog.require('proto.jspb.test.MapValueMessage'); goog.require('proto.jspb.test.TestMapFields'); goog.require('proto.jspb.test.TestMapFieldsOptionalKeys'); +goog.require('proto.jspb.test.TestMapFieldsOptionalValues'); goog.require('proto.jspb.test.MapEntryOptionalKeysStringKey'); goog.require('proto.jspb.test.MapEntryOptionalKeysInt32Key'); goog.require('proto.jspb.test.MapEntryOptionalKeysInt64Key'); goog.require('proto.jspb.test.MapEntryOptionalKeysBoolKey'); +goog.require('proto.jspb.test.MapEntryOptionalValuesStringValue'); +goog.require('proto.jspb.test.MapEntryOptionalValuesInt32Value'); +goog.require('proto.jspb.test.MapEntryOptionalValuesInt64Value'); +goog.require('proto.jspb.test.MapEntryOptionalValuesBoolValue'); +goog.require('proto.jspb.test.MapEntryOptionalValuesDoubleValue'); +goog.require('proto.jspb.test.MapEntryOptionalValuesEnumValue'); +goog.require('proto.jspb.test.MapEntryOptionalValuesMessageValue'); // CommonJS-LoadFromFile: test_pb proto.jspb.test goog.require('proto.jspb.test.MapValueMessageNoBinary'); @@ -54,7 +62,12 @@ var arr = map.toArray(); assertEquals(arr.length, entries.length); for (var i = 0; i < arr.length; i++) { - assertElementsEquals(arr[i], entries[i]); + if (Array.isArray(arr[i])) { + assertTrue(Array.isArray(entries[i])); + assertArrayEquals(arr[i], entries[i]); + } else { + assertElementsEquals(arr[i], entries[i]); + } } } @@ -265,8 +278,10 @@ var decoded = msgInfo.deserializeBinary(serialized); checkMapFields(decoded); }); + /** - * Tests deserialization of undefined map keys go to default values in binary format. + * Tests deserialization of undefined map keys go to default values in + * binary format. */ it('testMapDeserializationForUndefinedKeys', function() { var testMessageOptionalKeys = new proto.jspb.test.TestMapFieldsOptionalKeys(); @@ -298,6 +313,67 @@ [false, 'd'] ]); }); + + /** + * Tests deserialization of undefined map values go to default values in + * binary format. + */ + it('testMapDeserializationForUndefinedValues', function() { + var testMessageOptionalValues = + new proto.jspb.test.TestMapFieldsOptionalValues(); + var mapEntryStringValue = + new proto.jspb.test.MapEntryOptionalValuesStringValue(); + mapEntryStringValue.setKey("a"); + testMessageOptionalValues.setMapStringString(mapEntryStringValue); + var mapEntryInt32Value = + new proto.jspb.test.MapEntryOptionalValuesInt32Value(); + mapEntryInt32Value.setKey("b"); + testMessageOptionalValues.setMapStringInt32(mapEntryInt32Value); + var mapEntryInt64Value = + new proto.jspb.test.MapEntryOptionalValuesInt64Value(); + mapEntryInt64Value.setKey("c"); + testMessageOptionalValues.setMapStringInt64(mapEntryInt64Value); + var mapEntryBoolValue = + new proto.jspb.test.MapEntryOptionalValuesBoolValue(); + mapEntryBoolValue.setKey("d"); + testMessageOptionalValues.setMapStringBool(mapEntryBoolValue); + var mapEntryDoubleValue = + new proto.jspb.test.MapEntryOptionalValuesDoubleValue(); + mapEntryDoubleValue.setKey("e"); + testMessageOptionalValues.setMapStringDouble(mapEntryDoubleValue); + var mapEntryEnumValue = + new proto.jspb.test.MapEntryOptionalValuesEnumValue(); + mapEntryEnumValue.setKey("f"); + testMessageOptionalValues.setMapStringEnum(mapEntryEnumValue); + var mapEntryMessageValue = + new proto.jspb.test.MapEntryOptionalValuesMessageValue(); + mapEntryMessageValue.setKey("g"); + testMessageOptionalValues.setMapStringMsg(mapEntryMessageValue); + var deserializedMessage = msgInfo.deserializeBinary( + testMessageOptionalValues.serializeBinary() + ); + checkMapEquals(deserializedMessage.getMapStringStringMap(), [ + ['a', ''] + ]); + checkMapEquals(deserializedMessage.getMapStringInt32Map(), [ + ['b', 0] + ]); + checkMapEquals(deserializedMessage.getMapStringInt64Map(), [ + ['c', 0] + ]); + checkMapEquals(deserializedMessage.getMapStringBoolMap(), [ + ['d', false] + ]); + checkMapEquals(deserializedMessage.getMapStringDoubleMap(), [ + ['e', 0.0] + ]); + checkMapEquals(deserializedMessage.getMapStringEnumMap(), [ + ['f', 0] + ]); + checkMapEquals(deserializedMessage.getMapStringMsgMap(), [ + ['g', []] + ]); + }); }
diff --git a/js/message.js b/js/message.js index 0957c6d..52c541e 100644 --- a/js/message.js +++ b/js/message.js
@@ -1860,7 +1860,6 @@ * @param {Function} constructor The message constructor. */ jspb.Message.registerMessageType = function(id, constructor) { - jspb.Message.registry_[id] = constructor; // This is needed so we can later access messageId directly on the contructor, // otherwise it is not available due to 'property collapsing' by the compiler. /** @@ -1868,15 +1867,6 @@ */ constructor.messageId = id; }; - - -/** - * The registry of message ids to message constructors. - * @private - */ -jspb.Message.registry_ = {}; - - /** * The extensions registered on MessageSet. This is a map of extension * field number to field info object. This should be considered as a
diff --git a/js/package.json b/js/package.json index 202e8e4..37dfe67 100644 --- a/js/package.json +++ b/js/package.json
@@ -8,11 +8,11 @@ ], "dependencies": {}, "devDependencies": { - "glob": "~6.0.4", - "google-closure-compiler": "~20190301.0.0", - "google-closure-library": "~20190301.0.0", - "gulp": "~4.0.1", - "jasmine": "~2.4.1" + "glob": "~7.1.4", + "google-closure-compiler": "~20190819.0.0", + "google-closure-library": "~20190819.0.0", + "gulp": "~4.0.2", + "jasmine": "~3.4.0" }, "scripts": { "test": "node ./node_modules/gulp/bin/gulp.js test"
diff --git a/js/testbinary.proto b/js/testbinary.proto index 2e54845..a141285 100644 --- a/js/testbinary.proto +++ b/js/testbinary.proto
@@ -232,6 +232,56 @@ // End mock-map entries +// These proto are 'mock map' entries to test the above map deserializing with +// undefined values. Make sure TestMapFieldsOptionalValues is written to be +// deserialized by TestMapFields +message MapEntryOptionalValuesStringValue { + optional string key = 1; + optional string value = 2; +} + +message MapEntryOptionalValuesInt32Value { + optional string key = 1; + optional int32 value = 2; +} + +message MapEntryOptionalValuesInt64Value { + optional string key = 1; + optional int64 value = 2; +} + +message MapEntryOptionalValuesBoolValue { + optional string key = 1; + optional bool value = 2; +} + +message MapEntryOptionalValuesDoubleValue { + optional string key = 1; + optional double value = 2; +} + +message MapEntryOptionalValuesEnumValue { + optional string key = 1; + optional MapValueEnum value = 2; +} + +message MapEntryOptionalValuesMessageValue { + optional string key = 1; + optional MapValueMessage value = 2; +} + +message TestMapFieldsOptionalValues { + optional MapEntryOptionalValuesStringValue map_string_string = 1; + optional MapEntryOptionalValuesInt32Value map_string_int32 = 2; + optional MapEntryOptionalValuesInt64Value map_string_int64 = 3; + optional MapEntryOptionalValuesBoolValue map_string_bool = 4; + optional MapEntryOptionalValuesDoubleValue map_string_double = 5; + optional MapEntryOptionalValuesEnumValue map_string_enum = 6; + optional MapEntryOptionalValuesMessageValue map_string_msg = 7; +} + +// End mock-map entries + enum MapValueEnum { MAP_VALUE_FOO = 0; MAP_VALUE_BAR = 1;