diff --git a/.gitignore b/.gitignore index a1690e7..638cee5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea +.vscode node_modules commonjs_out google/protobuf @@ -7,3 +8,6 @@ bazel-* /testproto_libs1.js /testproto_libs2.js /google-protobuf.js +/google-protobuf-*.tgz +/conformance/protos/*.js +/conformance/package-lock.json diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index f2f6378..7299fdd 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -23,8 +23,9 @@ "https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58", "https://bcr.bazel.build/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b", "https://bcr.bazel.build/modules/bazel_features/1.23.0/MODULE.bazel": "fd1ac84bc4e97a5a0816b7fd7d4d4f6d837b0047cf4cbd81652d616af3a6591a", - "https://bcr.bazel.build/modules/bazel_features/1.23.0/source.json": "c72c61b722d7c3f884994fe647afeb2ed1ae66c437f8f370753551f7b4d8be7f", "https://bcr.bazel.build/modules/bazel_features/1.3.0/MODULE.bazel": "cdcafe83ec318cda34e02948e81d790aab8df7a929cec6f6969f13a489ccecd9", + "https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87", + "https://bcr.bazel.build/modules/bazel_features/1.30.0/source.json": "b07e17f067fe4f69f90b03b36ef1e08fe0d1f3cac254c1241a1818773e3423bc", "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", @@ -89,11 +90,12 @@ "https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc", "https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87", "https://bcr.bazel.build/modules/rules_cc/0.0.17/MODULE.bazel": "2ae1d8f4238ec67d7185d8861cb0a2cdf4bc608697c331b95bf990e69b62e64a", - "https://bcr.bazel.build/modules/rules_cc/0.0.17/source.json": "4db99b3f55c90ab28d14552aa0632533e3e8e5e9aea0f5c24ac0014282c2a7c5", "https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c", "https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f", "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", + "https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513", + "https://bcr.bazel.build/modules/rules_cc/0.1.1/source.json": "d61627377bd7dd1da4652063e368d9366fc9a73920bfa396798ad92172cf645c", "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", @@ -106,8 +108,8 @@ "https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab", "https://bcr.bazel.build/modules/rules_java/7.3.2/MODULE.bazel": "50dece891cfdf1741ea230d001aa9c14398062f2b7c066470accace78e412bc2", "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", - "https://bcr.bazel.build/modules/rules_java/8.11.0/MODULE.bazel": "c3d280bc5ff1038dcb3bacb95d3f6b83da8dd27bba57820ec89ea4085da767ad", - "https://bcr.bazel.build/modules/rules_java/8.11.0/source.json": "302b52a39259a85aa06ca3addb9787864ca3e03b432a5f964ea68244397e7544", + "https://bcr.bazel.build/modules/rules_java/8.12.0/MODULE.bazel": "8e6590b961f2defdfc2811c089c75716cb2f06c8a4edeb9a8d85eaa64ee2a761", + "https://bcr.bazel.build/modules/rules_java/8.12.0/source.json": "cbd5d55d9d38d4008a7d00bee5b5a5a4b6031fcd4a56515c9accbcd42c7be2ba", "https://bcr.bazel.build/modules/rules_java/8.3.2/MODULE.bazel": "7336d5511ad5af0b8615fdc7477535a2e4e723a357b6713af439fe8cf0195017", "https://bcr.bazel.build/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939", "https://bcr.bazel.build/modules/rules_java/8.6.1/MODULE.bazel": "f4808e2ab5b0197f094cabce9f4b006a27766beb6a9975931da07099560ca9c2", @@ -162,7 +164,6 @@ "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/source.json": "32bd87e5f4d7acc57c5b2ff7c325ae3061d5e242c0c4c214ae87e0f1c13e54cb", "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", - "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/MODULE.bazel": "af322bc08976524477c79d1e45e241b6efbeb918c497e8840b8ab116802dda79", "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca", "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806", "https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198" @@ -171,7 +172,7 @@ "moduleExtensions": { "@@apple_support+//crosstool:setup.bzl%apple_cc_configure_extension": { "general": { - "bzlTransitiveDigest": "okb7JAyJ9zeL+SDmtbWT0XBLq8WRoLJ0zWAG783RLVI=", + "bzlTransitiveDigest": "E970FlMbwpgJPdPUQzatKh6BMfeE0ZpWABvwshh7Tmg=", "usagesDigest": "EJfdUgbJdIRboG70S9dz8HhGVkL2/s3qFQ9xht8y2Rs=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -202,7 +203,7 @@ }, "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { "general": { - "bzlTransitiveDigest": "sFhcgPbDQehmbD1EOXzX4H1q/CD5df8zwG4kp4jbvr8=", + "bzlTransitiveDigest": "hUTp2w+RUVdL7ma5esCXZJAFnX7vLbVfLd7FwnQI6bU=", "usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, diff --git a/binary/any_field_type.js b/binary/any_field_type.js new file mode 100755 index 0000000..1ab21b9 --- /dev/null +++ b/binary/any_field_type.js @@ -0,0 +1,14 @@ +goog.module('jspb.binary.any_field_type'); + +const {RepeatedFieldType} = goog.require('jspb.binary.repeated_field_type'); +const {ScalarFieldType} = goog.requireType('jspb.binary.scalar_field_type'); + + +/** + * A field in jspb can be a scalar, a block of bytes, another proto, or an + * array of any of the above. + * @typedef {?ScalarFieldType|?RepeatedFieldType|!Uint8Array} + */ +let AnyFieldType; + +exports = {AnyFieldType}; diff --git a/binary/arith.js b/binary/arith.js old mode 100644 new mode 100755 index 97c73a4..9f084f5 --- a/binary/arith.js +++ b/binary/arith.js @@ -1,33 +1,3 @@ -// Protocol Buffers - Google's data interchange format -// Copyright 2008 Google Inc. All rights reserved. -// https://protobuf.dev/ -// -// 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 This file contains helper code used by jspb.utils to * handle 64-bit integer conversion to/from strings. @@ -36,403 +6,185 @@ * * TODO(haberman): move this to javascript/closure/math? */ +goog.module('jspb.arith'); +goog.module.declareLegacyNamespace(); -goog.provide('jspb.arith.Int64'); -goog.provide('jspb.arith.UInt64'); +const {getSplit64High, getSplit64Low, joinSignedDecimalString, joinUnsignedDecimalString, splitDecimalString} = goog.require('jspb.utils'); /** * UInt64 implements some 64-bit arithmetic routines necessary for properly - * handling 64-bit integer fields. It implements lossless integer arithmetic on - * top of JavaScript's number type, which has only 53 bits of precision, by - * representing 64-bit integers as two 32-bit halves. - * - * @param {number} lo The low 32 bits. - * @param {number} hi The high 32 bits. - * @constructor - * @export + * handling 64-bit integer fields from protobuf fields with decimal string + * conversions. + * @final */ -jspb.arith.UInt64 = function(lo, hi) { - /** - * The low 32 bits. - * @type {number} - * @export - */ - this.lo = lo; +class UInt64 { /** - * The high 32 bits. - * @type {number} - * @export + * @param {number} lo The low 32 bits. + * @param {number} hi The high 32 bits. */ - this.hi = hi; -}; - - -/** - * Compare two 64-bit numbers. Returns -1 if the first is - * less, +1 if the first is greater, or 0 if both are equal. - * @param {!jspb.arith.UInt64} other - * @return {number} - * @export - */ -jspb.arith.UInt64.prototype.cmp = function(other) { - if (this.hi < other.hi || (this.hi == other.hi && this.lo < other.lo)) { - return -1; - } else if (this.hi == other.hi && this.lo == other.lo) { - return 0; - } else { - return 1; + constructor(lo, hi) { + /** + * The low 32 bits, always stored as uint32. + * @public @const {number} + */ + this.lo = lo >>> 0; + + /** + * The high 32 bits, always stored as uint32. + * @public @const {number} + */ + this.hi = hi >>> 0; } -}; - - -/** - * Right-shift this number by one bit. - * @return {!jspb.arith.UInt64} - * @export - */ -jspb.arith.UInt64.prototype.rightShift = function() { - var hi = this.hi >>> 1; - var lo = (this.lo >>> 1) | ((this.hi & 1) << 31); - return new jspb.arith.UInt64(lo >>> 0, hi >>> 0); -}; - - -/** - * Left-shift this number by one bit. - * @return {!jspb.arith.UInt64} - * @export - */ -jspb.arith.UInt64.prototype.leftShift = function() { - var lo = this.lo << 1; - var hi = (this.hi << 1) | (this.lo >>> 31); - return new jspb.arith.UInt64(lo >>> 0, hi >>> 0); -}; - - -/** - * Test the MSB. - * @return {boolean} - * @export - */ -jspb.arith.UInt64.prototype.msb = function() { - return !!(this.hi & 0x80000000); -}; - - -/** - * Test the LSB. - * @return {boolean} - * @export - */ -jspb.arith.UInt64.prototype.lsb = function() { - return !!(this.lo & 1); -}; - - -/** - * Test whether this number is zero. - * @return {boolean} - * @export - */ -jspb.arith.UInt64.prototype.zero = function() { - return this.lo == 0 && this.hi == 0; -}; - - -/** - * Add two 64-bit numbers to produce a 64-bit number. - * @param {!jspb.arith.UInt64} other - * @return {!jspb.arith.UInt64} - * @export - */ -jspb.arith.UInt64.prototype.add = function(other) { - var lo = ((this.lo + other.lo) & 0xffffffff) >>> 0; - var hi = - (((this.hi + other.hi) & 0xffffffff) >>> 0) + - (((this.lo + other.lo) >= 0x100000000) ? 1 : 0); - return new jspb.arith.UInt64(lo >>> 0, hi >>> 0); -}; - - -/** - * Subtract two 64-bit numbers to produce a 64-bit number. - * @param {!jspb.arith.UInt64} other - * @return {!jspb.arith.UInt64} - * @export - */ -jspb.arith.UInt64.prototype.sub = function(other) { - var lo = ((this.lo - other.lo) & 0xffffffff) >>> 0; - var hi = - (((this.hi - other.hi) & 0xffffffff) >>> 0) - - (((this.lo - other.lo) < 0) ? 1 : 0); - return new jspb.arith.UInt64(lo >>> 0, hi >>> 0); -}; - - -/** - * Multiply two 32-bit numbers to produce a 64-bit number. - * @param {number} a The first integer: must be in [0, 2^32-1). - * @param {number} b The second integer: must be in [0, 2^32-1). - * @return {!jspb.arith.UInt64} - * @export - */ -jspb.arith.UInt64.mul32x32 = function(a, b) { - // Directly multiplying two 32-bit numbers may produce up to 64 bits of - // precision, thus losing precision because of the 53-bit mantissa of - // JavaScript numbers. So we multiply with 16-bit digits (radix 65536) - // instead. - var aLow = (a & 0xffff); - var aHigh = (a >>> 16); - var bLow = (b & 0xffff); - var bHigh = (b >>> 16); - var productLow = - // 32-bit result, result bits 0-31, take all 32 bits - (aLow * bLow) + - // 32-bit result, result bits 16-47, take bottom 16 as our top 16 - ((aLow * bHigh) & 0xffff) * 0x10000 + - // 32-bit result, result bits 16-47, take bottom 16 as our top 16 - ((aHigh * bLow) & 0xffff) * 0x10000; - var productHigh = - // 32-bit result, result bits 32-63, take all 32 bits - (aHigh * bHigh) + - // 32-bit result, result bits 16-47, take top 16 as our bottom 16 - ((aLow * bHigh) >>> 16) + - // 32-bit result, result bits 16-47, take top 16 as our bottom 16 - ((aHigh * bLow) >>> 16); - - // Carry. Note that we actually have up to *two* carries due to addition of - // three terms. - while (productLow >= 0x100000000) { - productLow -= 0x100000000; - productHigh += 1; - } - - return new jspb.arith.UInt64(productLow >>> 0, productHigh >>> 0); -}; - - -/** - * Multiply this number by a 32-bit number, producing a 96-bit number, then - * truncate the top 32 bits. - * @param {number} a The multiplier. - * @return {!jspb.arith.UInt64} - * @export - */ -jspb.arith.UInt64.prototype.mul = function(a) { - // Produce two parts: at bits 0-63, and 32-95. - var lo = jspb.arith.UInt64.mul32x32(this.lo, a); - var hi = jspb.arith.UInt64.mul32x32(this.hi, a); - // Left-shift hi by 32 bits, truncating its top bits. The parts will then be - // aligned for addition. - hi.hi = hi.lo; - hi.lo = 0; - return lo.add(hi); -}; - - -/** - * Divide a 64-bit number by a 32-bit number to produce a - * 64-bit quotient and a 32-bit remainder. - * @param {number} _divisor - * @return {Array} array of [quotient, remainder], - * unless divisor is 0, in which case an empty array is returned. - * @export - */ -jspb.arith.UInt64.prototype.div = function(_divisor) { - if (_divisor == 0) { - return []; - } - - // We perform long division using a radix-2 algorithm, for simplicity (i.e., - // one bit at a time). TODO: optimize to a radix-2^32 algorithm, taking care - // to get the variable shifts right. - var quotient = new jspb.arith.UInt64(0, 0); - var remainder = new jspb.arith.UInt64(this.lo, this.hi); - var divisor = new jspb.arith.UInt64(_divisor, 0); - var unit = new jspb.arith.UInt64(1, 0); - // Left-shift the divisor and unit until the high bit of divisor is set. - while (!divisor.msb()) { - divisor = divisor.leftShift(); - unit = unit.leftShift(); + /** + * Convert a 64-bit number to a string. + * @return {string} + */ + toDecimalString() { + return joinUnsignedDecimalString(this.lo, this.hi); } - // Perform long division one bit at a time. - while (!unit.zero()) { - // If divisor < remainder, add unit to quotient and subtract divisor from - // remainder. - if (divisor.cmp(remainder) <= 0) { - quotient = quotient.add(unit); - remainder = remainder.sub(divisor); + /** + * Negates this uint64 in twos-complement. + * + * @return {!UInt64} + */ + negateInTwosComplement() { + if (this.lo === 0) { + return new UInt64(0, 1 + ~this.hi); } - // Right-shift the divisor and unit. - divisor = divisor.rightShift(); - unit = unit.rightShift(); + return new UInt64(~this.lo + 1, ~this.hi); } - return [quotient, remainder]; -}; - - -/** - * Convert a 64-bit number to a string. - * @return {string} - * @override - * @export - */ -jspb.arith.UInt64.prototype.toString = function() { - var result = ''; - var num = this; - while (!num.zero()) { - var divResult = num.div(10); - var quotient = divResult[0], remainder = divResult[1]; - result = remainder.lo + result; - num = quotient; - } - if (result == '') { - result = '0'; + /** + * Construct a Uint64 from a bigint + * + * @param {bigint} n + * @return {!UInt64} + */ + static fromBigInt(n) { + const asU64 = BigInt.asUintN(64, n); + return new UInt64( + /* lowBits = */ Number((asU64 & BigInt(0xFFFFFFFF))), + /* highBits = */ Number(asU64 >> BigInt(32))); } - return result; -}; - -/** - * Parse a string into a 64-bit number. Returns `null` on a parse error. - * @param {string} s - * @return {?jspb.arith.UInt64} - * @export - */ -jspb.arith.UInt64.fromString = function(s) { - var result = new jspb.arith.UInt64(0, 0); - // optimization: reuse this instance for each digit. - var digit64 = new jspb.arith.UInt64(0, 0); - for (var i = 0; i < s.length; i++) { - if (s[i] < '0' || s[i] > '9') { + /** + * Parse a string into a 64-bit number. Returns `null` on a parse error. + * + * @param {string} s + * @return {?UInt64} + */ + static fromString(s) { + if (!s) return UInt64.getZero(); + if (!/^\d+$/.test(s)) { + // TODO(web-protos-team): Throw an error rather than returning null. return null; } - var digit = parseInt(s[i], 10); - digit64.lo = digit; - result = result.mul(10).add(digit64); + splitDecimalString(s); + return new UInt64(getSplit64Low(), getSplit64High()); } - return result; -}; + /** + * Construct a Uint64 from a JavaScript number. + * @param {number} n + * @return {!UInt64} + */ + static fromNumber(n) { + return new UInt64(n & ALL_32_BITS, n / TWO_PWR_32_DBL); + } -/** - * Make a copy of the uint64. - * @return {!jspb.arith.UInt64} - * @export - */ -jspb.arith.UInt64.prototype.clone = function() { - return new jspb.arith.UInt64(this.lo, this.hi); -}; + static getZero() { + return uint64Zero || (uint64Zero = new UInt64(0, 0)); + } +} +let /** !UInt64|undefined */ uint64Zero; /** * Int64 is like UInt64, but modifies string conversions to interpret the stored - * 64-bit value as a twos-complement-signed integer. It does *not* support the - * full range of operations that UInt64 does: only add, subtract, and string + * 64-bit value as a twos-complement-signed integer with decimal string * conversions. - * - * N.B. that multiply and divide routines are *NOT* supported. They will throw - * exceptions. (They are not necessary to implement string conversions, which - * are the only operations we really need in jspb.) - * - * @param {number} lo The low 32 bits. - * @param {number} hi The high 32 bits. - * @constructor - * @export + * @final */ -jspb.arith.Int64 = function(lo, hi) { +class Int64 { /** - * The low 32 bits. - * @type {number} - * @export + * @param {number} lo The low 32 bits. + * @param {number} hi The high 32 bits. */ - this.lo = lo; + constructor(lo, hi) { + /** + * The low 32 bits, always stored as uint32. + * @public @const {number} + */ + this.lo = lo >>> 0; + + /** + * The high 32 bits, always stored as uint32. + * @public @const {number} + */ + this.hi = hi >>> 0; + } + /** - * The high 32 bits. - * @type {number} - * @export + * Convert a 64-bit number to a string. + * @return {string} */ - this.hi = hi; -}; - - -/** - * Add two 64-bit numbers to produce a 64-bit number. - * @param {!jspb.arith.Int64} other - * @return {!jspb.arith.Int64} - * @export - */ -jspb.arith.Int64.prototype.add = function(other) { - var lo = ((this.lo + other.lo) & 0xffffffff) >>> 0; - var hi = - (((this.hi + other.hi) & 0xffffffff) >>> 0) + - (((this.lo + other.lo) >= 0x100000000) ? 1 : 0); - return new jspb.arith.Int64(lo >>> 0, hi >>> 0); -}; + toDecimalString() { + return joinSignedDecimalString(this.lo, this.hi); + } + /** + * Construct a Int64 from a bigint + * + * @param {bigint} n + * @return {!Int64} + */ + static fromBigInt(n) { + const asUint64 = BigInt.asUintN(64, n); + return new Int64( + /* lowBits = */ Number((asUint64 & BigInt(0xFFFFFFFF))), + /* highBits = */ Number(asUint64 >> BigInt(32))); + } -/** - * Subtract two 64-bit numbers to produce a 64-bit number. - * @param {!jspb.arith.Int64} other - * @return {!jspb.arith.Int64} - * @export - */ -jspb.arith.Int64.prototype.sub = function(other) { - var lo = ((this.lo - other.lo) & 0xffffffff) >>> 0; - var hi = - (((this.hi - other.hi) & 0xffffffff) >>> 0) - - (((this.lo - other.lo) < 0) ? 1 : 0); - return new jspb.arith.Int64(lo >>> 0, hi >>> 0); -}; + /** + * Parse a string into a 64-bit number. Returns `null` on a parse error. + * @param {string} s + * @return {?Int64} + */ + static fromString(s) { + if (!s) return Int64.getZero(); + if (!/^-?\d+$/.test(s)) { + // TODO: Throw an error rather than returning null. + return null; + } + splitDecimalString(s); + return new Int64(getSplit64Low(), getSplit64High()); + } + /** + * Construct a Uint64 from a JavaScript number. + * @param {number} n + * @return {!Int64} + */ + static fromNumber(n) { + return new Int64(n & ALL_32_BITS, n / TWO_PWR_32_DBL); + } -/** - * Make a copy of the int64. - * @return {!jspb.arith.Int64} - * @export - */ -jspb.arith.Int64.prototype.clone = function() { - return new jspb.arith.Int64(this.lo, this.hi); -}; + static getZero() { + return int64Zero || (int64Zero = new Int64(0, 0)); + } +} +let /** !Int64|undefined */ int64Zero; -/** - * Convert a 64-bit number to a string. - * @return {string} - * @override - * @export - */ -jspb.arith.Int64.prototype.toString = function() { - // If the number is negative, find its twos-complement inverse. - var sign = (this.hi & 0x80000000) != 0; - var num = new jspb.arith.UInt64(this.lo, this.hi); - if (sign) { - num = new jspb.arith.UInt64(0, 0).sub(num); - } - return (sign ? '-' : '') + num.toString(); -}; +/** @const {number} */ +const ALL_32_BITS = 0xFFFFFFFF; +/** @const {number} */ +const TWO_PWR_32_DBL = 0x100000000; -/** - * Parse a string into a 64-bit number. Returns `null` on a parse error. - * @param {string} s - * @return {?jspb.arith.Int64} - * @export - */ -jspb.arith.Int64.fromString = function(s) { - var hasNegative = (s.length > 0 && s[0] == '-'); - if (hasNegative) { - s = s.substring(1); - } - var num = jspb.arith.UInt64.fromString(s); - if (num === null) { - return null; - } - if (hasNegative) { - num = new jspb.arith.UInt64(0, 0).sub(num); - } - return new jspb.arith.Int64(num.lo, num.hi); +exports = { + UInt64, + Int64, }; diff --git a/binary/arith_test.js b/binary/arith_test.js old mode 100644 new mode 100755 index 688b91a..a43a44a --- a/binary/arith_test.js +++ b/binary/arith_test.js @@ -39,242 +39,21 @@ goog.require('jspb.arith.Int64'); goog.require('jspb.arith.UInt64'); +const Int64 = goog.module.get('jspb.arith').Int64; +const UInt64 = goog.module.get('jspb.arith').UInt64; -describe('binaryArithTest', function() { - /** - * Tests comparison operations. - */ - it('testCompare', function() { - const a = new jspb.arith.UInt64(1234, 5678); - const b = new jspb.arith.UInt64(1234, 5678); - expect(a.cmp(b)).toEqual(0); - expect(b.cmp(a)).toEqual(0); - b.lo -= 1; - expect(a.cmp(b)).toEqual(1); - expect(b.cmp(a)).toEqual(-1); - b.lo += 2; - expect(a.cmp(b)).toEqual(-1); - expect(b.cmp(a)).toEqual(1); - b.lo = a.lo; - b.hi = a.hi - 1; - expect(a.cmp(b)).toEqual(1); - expect(b.cmp(a)).toEqual(-1); - expect(a.zero()).toEqual(false); - expect(a.msb()).toEqual(false); - expect(a.lsb()).toEqual(false); - a.hi = 0; - a.lo = 0; - expect(a.zero()).toEqual(true); - a.hi = 0x80000000; - expect(a.zero()).toEqual(false); - expect(a.msb()).toEqual(true); - a.lo = 0x00000001; - expect(a.lsb()).toEqual(true); - }); - - - /** - * Tests shifts. - */ - it('testShifts', function() { - let a = new jspb.arith.UInt64(1, 0); - expect(a.lo).toEqual(1); - expect(a.hi).toEqual(0); - const orig = a; - a = a.leftShift(); - expect(orig.lo).toEqual(1); // original unmodified. - expect(orig.hi).toEqual(0); - expect(a.lo).toEqual(2); - expect(a.hi).toEqual(0); - a = a.leftShift(); - expect(a.lo).toEqual(4); - expect(a.hi).toEqual(0); - for (let i = 0; i < 29; i++) { - a = a.leftShift(); - } - expect(a.lo).toEqual(0x80000000); - expect(a.hi).toEqual(0); - a = a.leftShift(); - expect(a.lo).toEqual(0); - expect(a.hi).toEqual(1); - a = a.leftShift(); - expect(a.lo).toEqual(0); - expect(a.hi).toEqual(2); - a = a.rightShift(); - a = a.rightShift(); - expect(a.lo).toEqual(0x80000000); - expect(a.hi).toEqual(0); - a = a.rightShift(); - expect(a.lo).toEqual(0x40000000); - expect(a.hi).toEqual(0); - }); - - - /** - * Tests additions. - */ - it('testAdd', function() { - const a = new jspb.arith.UInt64( - /* lo = */ 0x89abcdef, - /* hi = */ 0x01234567); - const b = new jspb.arith.UInt64( - /* lo = */ 0xff52ab91, - /* hi = */ 0x92fa2123); - // Addition with carry. - let c = a.add(b); - expect(a.lo).toEqual(0x89abcdef); // originals unmodified. - expect(a.hi).toEqual(0x01234567); - expect(b.lo).toEqual(0xff52ab91); - expect(b.hi).toEqual(0x92fa2123); - expect(c.lo).toEqual(0x88fe7980); - expect(c.hi).toEqual(0x941d668b); - - // Simple addition without carry. - a.lo = 2; - a.hi = 0; - b.lo = 3; - b.hi = 0; - c = a.add(b); - expect(c.lo).toEqual(5); - expect(c.hi).toEqual(0); - }); - - - /** - * Test subtractions. - */ - it('testSub', function() { - const kLength = 10; - const hiValues = [ - 0x1682ef32, 0x583902f7, 0xb62f5955, 0x6ea99bbf, 0x25a39c20, 0x0700a08b, - 0x00f7304d, 0x91a5b5af, 0x89077fd2, 0xe09e347c - ]; - const loValues = [ - 0xe1538b18, 0xbeacd556, 0x74100758, 0x96e3cb26, 0x56c37c3f, 0xe00b3f7d, - 0x859f25d7, 0xc2ee614a, 0xe1d21cd7, 0x30aae6a4 - ]; - for (let i = 0; i < kLength; i++) { - for (let j = 0; j < kLength; j++) { - const a = new jspb.arith.UInt64(loValues[i], hiValues[j]); - const b = new jspb.arith.UInt64(loValues[j], hiValues[i]); - const c = a.add(b).sub(b); - expect(c.hi).toEqual(a.hi); - expect(c.lo).toEqual(a.lo); - } - } - }); - - - /** - * Tests 32-by-32 multiplication. - */ - it('testMul32x32', function() { - const testData = [ - // a b low(a*b) high(a*b) - [0xc0abe2f8, 0x1607898a, 0x5de711b0, 0x109471b8], - [0x915eb3cb, 0x4fb66d0e, 0xbd0d441a, 0x2d43d0bc], - [0xfe4efe70, 0x80b48c37, 0xbcddea10, 0x7fdada0c], - [0xe222fd4a, 0xe43d524a, 0xd5e0eb64, 0xc99d549c], - [0xd171f469, 0xb94ebd01, 0x4be17969, 0x979bc4fa], - [0x829cc1df, 0xe2598b38, 0xf4157dc8, 0x737c12ad], - [0xf10c3767, 0x8382881e, 0x942b3612, 0x7bd428b8], - [0xb0f6dd24, 0x232597e1, 0x079c98a4, 0x184bbce7], - [0xfcdb05a7, 0x902f55bc, 0x636199a4, 0x8e69f412], - [0x0dd0bfa9, 0x916e27b1, 0x6e2542d9, 0x07d92e65] - ]; - - for (let i = 0; i < testData.length; i++) { - const a = testData[i][0] >>> 0; - const b = testData[i][1] >>> 0; - const cLow = testData[i][2] >>> 0; - const cHigh = testData[i][3] >>> 0; - const c = jspb.arith.UInt64.mul32x32(a, b); - expect(c.lo).toEqual(cLow); - expect(c.hi).toEqual(cHigh); - } - }); - - - /** - * Tests 64-by-32 multiplication. - */ - it('testMul', function() { - // 64x32 bits produces 96 bits of product. The multiplication function under - // test truncates the top 32 bits, so we compare against a 64-bit expected - // product. - const testData = [ - // low(a) high(a) low(a*b) high(a*b) - [0xec10955b, 0x360eb168, 0x4b7f3f5b, 0xbfcb7c59, 0x9517da5f], - [0x42b000fc, 0x9d101642, 0x6fa1ab72, 0x2584c438, 0x6a9e6d2b], - [0xf42d4fb4, 0xae366403, 0xa65a1000, 0x92434000, 0x1ff978df], - [0x17e2f56b, 0x25487693, 0xf13f98c7, 0x73794e2d, 0xa96b0c6a], - [0x492f241f, 0x76c0eb67, 0x7377ac44, 0xd4336c3c, 0xfc4b1ebe], - [0xd6b92321, 0xe184fa48, 0xd6e76904, 0x93141584, 0xcbf44da1], - [0x4bf007ea, 0x968c0a9e, 0xf5e4026a, 0x4fdb1ae4, 0x61b9fb7d], - [0x10a83be7, 0x2d685ba6, 0xc9e5fb7f, 0x2ad43499, 0x3742473d], - [0x2f261829, 0x1aca681a, 0x3d3494e3, 0x8213205b, 0x283719f8], - [0xe4f2ce21, 0x2e74b7bd, 0xd801b38b, 0xbc17feeb, 0xc6c44e0f] - ]; - - for (let i = 0; i < testData.length; i++) { - const a = new jspb.arith.UInt64(testData[i][0], testData[i][1]); - const prod = a.mul(testData[i][2]); - expect(prod.lo).toEqual(testData[i][3]); - expect(prod.hi).toEqual(testData[i][4]); - } - }); - - - /** - * Tests 64-div-by-32 division. - */ - it('testDiv', function() { - // Compute a/b, yielding quot = a/b and rem = a%b. - const testData = [ - // --- divisors in (0, 2^32-1) to test full divisor range - // low(a) high(a) b low(quot) high(quot) rem - [0x712443f1, 0xe85cefcc, 0xc1a7050b, 0x332c79ad, 0x00000001, 0x92ffa882], - [0x11912915, 0xb2699eb5, 0x30467cbe, 0xb21b4be4, 0x00000003, 0x283465dd], - [0x0d917982, 0x201f2a6e, 0x3f35bf03, 0x8217c8e4, 0x00000000, 0x153402d6], - [0xa072c108, 0x74020c96, 0xc60568fd, 0x95f9613e, 0x00000000, 0x3f4676c2], - [0xd845d5d8, 0xcdd235c4, 0x20426475, 0x6154e78b, 0x00000006, 0x202fb751], - [0xa4dbf71f, 0x9e90465e, 0xf08e022f, 0xa8be947f, 0x00000000, 0xbe43b5ce], - [0x3dbe627f, 0xa791f4b9, 0x28a5bd89, 0x1f5dfe93, 0x00000004, 0x02bf9ed4], - [0x5c1c53ee, 0xccf5102e, 0x198576e7, 0x07e3ae31, 0x00000008, 0x02ea8fb7], - [0xfef1e581, 0x04714067, 0xca6540c1, 0x059e73ec, 0x00000000, 0x31658095], - [0x1e2dd90c, 0x13dd6667, 0x8b2184c3, 0x248d1a42, 0x00000000, 0x4ca6d0c6], - // --- divisors in (0, 2^16-1) to test larger quotient high-words - // low(a) high(a) b low(quot) high(quot) rem - [0x86722b47, 0x2cd57c9a, 0x00003123, 0x2ae41b7a, 0x0000e995, 0x00000f99], - [0x1dd7884c, 0xf5e839bc, 0x00009eeb, 0x5c886242, 0x00018c21, 0x000099b6], - [0x5c53d625, 0x899fc7e5, 0x000087d7, 0xd625007a, 0x0001035c, 0x000019af], - [0x6932d932, 0x9d0a5488, 0x000051fb, 0x9d976143, 0x0001ea63, 0x00004981], - [0x4d18bb85, 0x0c92fb31, 0x00001d9f, 0x03265ab4, 0x00006cac, 0x000001b9], - [0xbe756768, 0xdea67ccb, 0x00008a03, 0x58add442, 0x00019cff, 0x000056a2], - [0xe2466f9a, 0x2521f114, 0x0000c350, 0xa0c0860d, 0x000030ab, 0x0000a48a], - [0xf00ddad1, 0xe2f5446a, 0x00002cfc, 0x762697a6, 0x00050b96, 0x00000b69], - [0xa879152a, 0x0a70e0a5, 0x00007cdf, 0xb44151b3, 0x00001567, 0x0000363d], - [0x7179a74c, 0x46083fff, 0x0000253c, 0x4d39ba6e, 0x0001e17f, 0x00000f84] - ]; - - for (let i = 0; i < testData.length; i++) { - const a = new jspb.arith.UInt64(testData[i][0], testData[i][1]); - const result = a.div(testData[i][2]); - const quotient = result[0]; - const remainder = result[1]; - expect(quotient.lo).toEqual(testData[i][3]); - expect(quotient.hi).toEqual(testData[i][4]); - expect(remainder.lo).toEqual(testData[i][5]); - } - }); - - - /** - * Tests .toString() and .fromString(). - */ - it('testStrings', function() { - const testData = [ +describe('binaryArithTest', () => { + describe('UInt64', () => { + const /** ReadonlyArray<[number, number, string]> */ testData = Object.freeze([ + [0x0, 0x0, '0'], + [0xffffffff, 0xffffffff, '18446744073709551615'], + [0xffffffff, 0x00000000, '4294967295'], + [0x00000000, 0xffffffff, '18446744069414584320'], + [0x000f4240, 0x00000000, '1000000'], + [0x000f423f, 0x00000000, '999999'], + [0xd4a51000, 0x0000000e8, '1000000000000'], + [0xd4a50fff, 0x0000000e8, '999999999999'], [0x5e84c935, 0xcae33d0e, '14619595947299359029'], [0x62b3b8b8, 0x93480544, '10612738313170434232'], [0x319bfb13, 0xc01c4172, '13843011313344445203'], @@ -294,37 +73,204 @@ describe('binaryArithTest', function() { [0x4a843d8a, 0x864e132b, '9677693725920476554'], [0x25b4e94d, 0x22b54dc6, '2500990681505655117'], [0x6bbe664b, 0x55a5cc0e, '6171563226690381387'], - [0xee916c81, 0xb00aabb3, '12685140089732426881'] - ]; + [0xee916c81, 0xb00aabb3, '12685140089732426881'], + ]); + + // If BigInt is available, verify our test data with that. + if (typeof BigInt !== 'undefined') { + it('is tested with valid test data as verified with BigInt', () => { + for (const testCase of testData) { + const big = (BigInt(testCase[1]) << BigInt(32)) + BigInt(testCase[0]); + expect(big.toString()).toEqual(testCase[2]); + } + }); - for (let i = 0; i < testData.length; i++) { - const a = new jspb.arith.UInt64(testData[i][0], testData[i][1]); - const roundtrip = jspb.arith.UInt64.fromString(a.toString()); - expect(roundtrip.lo).toEqual(a.lo); - expect(roundtrip.hi).toEqual(a.hi); - expect(a.toString()).toEqual(testData[i][2]); + describe('fromBigInt', () => { + it(`parses testData`, () => { + for (const testCase of testData) { + const roundtrip = UInt64.fromBigInt(BigInt(testCase[2])); + expect(roundtrip.lo).withContext(testCase[2]).toEqual(testCase[0]); + expect(roundtrip.hi).withContext(testCase[2]).toEqual(testCase[1]); + } + }); + }); } + + describe('toDecimalString', () => { + it(`serializes testData`, () => { + for (const testCase of testData) { + const a = new UInt64(testCase[0], testCase[1]); + const actualString = a.toDecimalString(); + expect(actualString).withContext(testCase[2]).toEqual(testCase[2]); + } + }); + }); + + describe('fromString', () => { + it(`parses testData`, () => { + for (const testCase of testData) { + const roundtrip = UInt64.fromString(testCase[2]); + expect(roundtrip.lo).withContext(testCase[2]).toEqual(testCase[0]); + expect(roundtrip.hi).withContext(testCase[2]).toEqual(testCase[1]); + } + }); + + it('parses empty string as zero', () => { + expect(UInt64.fromString('')).toEqual(UInt64.fromString('0')); + }); + + it('does not parse non-decimal strings', () => { + expect(UInt64.fromString('0x123')).toBeNull(); + expect(UInt64.fromString(' 123')).toBeNull(); + }); + + it('tolerates extra leading zeros', () => { + expect(UInt64.fromString('0018446744073709551615')).toEqual( + UInt64.fromString('18446744073709551615'), + ); + }); + + it('truncates values that are too big', () => { + // 0x1_FFFF_FFFF_FFFF_FFFF = 36893488147419103231 + // 0xFFFF_FFFF_FFFF_FFFF = 18446744073709551615 + expect(UInt64.fromString('36893488147419103231')).toEqual( + UInt64.fromString('18446744073709551615'), + ); + }); + }); + + describe('negateInTwosComplement', () => { + if (typeof BigInt !== 'undefined') { + it('produces correct values', () => { + for (const testCase of testData) { + if (testCase[0] === 0 && testCase[1] === 0) continue; + const result = new UInt64( + testCase[0], + testCase[1], + ).negateInTwosComplement(); + const resultBigInt = + (BigInt(result.hi) << BigInt(32)) + BigInt(result.lo); + const expected = + (BigInt(1) << BigInt(64)) - + ((BigInt(testCase[1]) << BigInt(32)) + BigInt(testCase[0])); + expect(resultBigInt).toEqual(expected); + } + }); + } + + it('round-trips', () => { + for (const testCase of testData) { + expect( + new UInt64(testCase[0], testCase[1]) + .negateInTwosComplement() + .negateInTwosComplement() + .toDecimalString(), + ).toBe(testCase[2]); + } + }); + }); }); + describe('Int64', () => { + const /** ReadonlyArray<[number, number, string]> */ testData = Object.freeze([ + [0x0, 0x0, '0'], + [0xffffffff, 0xffffffff, '-1'], + [0x00000000, 0x80000000, '-9223372036854775808'], + [0xffffffff, 0x3fffffff, '4611686018427387903'], + [0xffffffff, 0x00000000, '4294967295'], + [0x00000000, 0xffffffff, '-4294967296'], + [0x000f4240, 0x00000000, '1000000'], + [0xd4a51000, 0x0000000e8, '1000000000000'], + [0xc5bb087e, 0x931814a6, '-7847499644178593666'], + [0xb58a5643, 0x3458a55b, '3771946501229139523'], + [0x7113da74, 0x27de6fcf, '2872856549054995060'], + [0x9d22f8c8, 0xafc92384, '-5780049594274350904'], + [0x48c45eb1, 0x2ef59f66, '3383785956695105201'], + [0x4f0a03e2, 0x294269f0, '2973055184857072610'], + [0xd4e95c3a, 0xca29805e, '-3879428459215627206'], + [0x934049d7, 0x3fb24a16, '4589812431064156631'], + [0xd272e654, 0x75bd7dc4, '8484075557333689940'], + [0x21f03f77, 0xeec5322, '1075325817098092407'], + [0x12158d26, 0xc3ad6da7, '-4346697501012292314'], + [0x7247925d, 0x22895b1f, '2488620459718316637'], + [0xb8d3e7a0, 0x54d48381, '6112655187423520672'], + [0xbf4836f8, 0xcd45d7c6, '-3655278273928612104'], + [0xf853f23c, 0x2fba545b, '3439154019435803196'], + [0xc004fc2d, 0xdef52fa, '1004112478843763757'], + [0x394e4b63, 0xa4937731, '-6587790776614368413'], + [0x596ed01a, 0x09382394, '664320065099714586'], + [0xe3244770, 0x42106251, '4760412909973292912'], + [0x92c2b290, 0x9233453d, '-7911903989602274672'], + ]); - /** - * Tests signed Int64s. These are built on UInt64s, so we only need to test - * the explicit overrides: .toString() and .fromString(). - */ - it('testSignedInt64', function() { - const testStrings = [ - '-7847499644178593666', '3771946501229139523', '2872856549054995060', - '-5780049594274350904', '3383785956695105201', '2973055184857072610', - '-3879428459215627206', '4589812431064156631', '8484075557333689940', - '1075325817098092407', '-4346697501012292314', '2488620459718316637', - '6112655187423520672', '-3655278273928612104', '3439154019435803196', - '1004112478843763757', '-6587790776614368413', '664320065099714586', - '4760412909973292912', '-7911903989602274672' - ]; + // If BigInt is available, verify our test data with that. + if (typeof BigInt !== 'undefined') { + it('is tested with valid test data as verified with BigInt', () => { + for (const testCase of testData) { + let big = (BigInt(testCase[1]) << BigInt(32)) + BigInt(testCase[0]); + if (testCase[2][0] === '-') { + big -= BigInt(2 ** 64); + } + expect(big.toString()).toEqual(testCase[2]); + } + }); - for (let i = 0; i < testStrings.length; i++) { - const roundtrip = jspb.arith.Int64.fromString(testStrings[i]).toString(); - expect(roundtrip).toEqual(testStrings[i]); + describe('fromBigInt', () => { + it(`parses testData`, () => { + for (const testCase of testData) { + const roundtrip = Int64.fromBigInt(BigInt(testCase[2])); + expect(roundtrip.lo).withContext(testCase[2]).toEqual(testCase[0]); + expect(roundtrip.hi).withContext(testCase[2]).toEqual(testCase[1]); + } + }); + }); } + + describe('toDecimalString', () => { + it(`serializes testData`, () => { + for (const testCase of testData) { + const a = new Int64(testCase[0], testCase[1]); + const actualString = a.toDecimalString(); + expect(actualString).withContext(testCase[2]).toEqual(testCase[2]); + } + }); + }); + + describe('fromString', () => { + it(`parses testData`, () => { + for (const testCase of testData) { + const roundtrip = Int64.fromString(testCase[2]); + expect(roundtrip.lo).withContext(testCase[2]).toEqual(testCase[0]); + expect(roundtrip.hi).withContext(testCase[2]).toEqual(testCase[1]); + } + }); + + it('parses empty string as zero', () => { + expect(Int64.fromString('')).toEqual(Int64.fromString('0')); + }); + + it('parses -0 string as zero', () => { + expect(Int64.fromString('-0')).toEqual(Int64.fromString('0')); + }); + + it('parses positive values that overflow into the negative space', () => { + expect( + Int64.fromString('9223372036854775808')?.toDecimalString(), + ).toEqual('-9223372036854775808'); + }); + + it('truncates values that are too big', () => { + // 0x1_FFFF_FFFF_FFFF_FFFF = 36893488147419103231 + // 0xFFFF_FFFF_FFFF_FFFF = 18446744073709551615 + expect(Int64.fromString('36893488147419103231')).toEqual( + Int64.fromString('18446744073709551615'), + ); + }); + + it('does not parse non-decimal strings', () => { + expect(Int64.fromString('0x123')).toBeNull(); + expect(Int64.fromString(' 123')).toBeNull(); + }); + }); }); }); diff --git a/binary/binary_constants.js b/binary/binary_constants.js new file mode 100755 index 0000000..a93caf9 --- /dev/null +++ b/binary/binary_constants.js @@ -0,0 +1,241 @@ +goog.module('jspb.BinaryConstants'); +goog.module.declareLegacyNamespace(); + + +/** + * Field type codes, taken from proto2/public/wire_format_lite.h. + * @enum {number} + * @package + */ +const FieldType = { + INVALID: -1, + DOUBLE: 1, + FLOAT: 2, + INT64: 3, + UINT64: 4, + INT32: 5, + FIXED64: 6, + FIXED32: 7, + BOOL: 8, + STRING: 9, + GROUP: 10, + MESSAGE: 11, + BYTES: 12, + UINT32: 13, + ENUM: 14, + SFIXED32: 15, + SFIXED64: 16, + SINT32: 17, + SINT64: 18, +}; + + +/** + * Wire-format type codes, taken from proto2/public/wire_format_lite.h. + * @enum {number} + */ +const WireType = { + INVALID: -1, + VARINT: 0, + FIXED64: 1, + DELIMITED: 2, + START_GROUP: 3, + END_GROUP: 4, + FIXED32: 5 +}; + +/** @return {boolean} */ +function isValidWireType(/** number */ wireType) { + return wireType >= 0 && wireType <= 5; +} + + +/** + * Translates field type to wire type. + * @param {!FieldType} fieldType + * @return {!WireType} + */ +function FieldTypeToWireType(fieldType) { + switch (fieldType) { + case FieldType.INT32: + case FieldType.INT64: + case FieldType.UINT32: + case FieldType.UINT64: + case FieldType.SINT32: + case FieldType.SINT64: + case FieldType.BOOL: + case FieldType.ENUM: + return WireType.VARINT; + + case FieldType.DOUBLE: + case FieldType.FIXED64: + case FieldType.SFIXED64: + return WireType.FIXED64; + + case FieldType.STRING: + case FieldType.MESSAGE: + case FieldType.BYTES: + return WireType.DELIMITED; + + case FieldType.FLOAT: + case FieldType.FIXED32: + case FieldType.SFIXED32: + return WireType.FIXED32; + + case FieldType.INVALID: + case FieldType.GROUP: + default: + return WireType.INVALID; + } +} + + +/** + * Flag to indicate a missing field. + * @const {number} + */ +const INVALID_FIELD_NUMBER = -1; + +/** + * Flag to indicate a missing tag. + * @const {number} + */ +const INVALID_TAG = -1; + + +/** + * The smallest denormal float32 value. + * @const {number} + */ +const FLOAT32_EPS = 1.401298464324817e-45; + + +/** + * The smallest normal float64 value. + * @const {number} + */ +const FLOAT32_MIN = 1.1754943508222875e-38; + + +/** + * The largest finite float32 value. + * @const {number} + */ +const FLOAT32_MAX = 3.4028234663852886e+38; + + +/** + * The smallest denormal float64 value. + * @const {number} + */ +const FLOAT64_EPS = 5e-324; + + +/** + * The smallest normal float64 value. + * @const {number} + */ +const FLOAT64_MIN = 2.2250738585072014e-308; + + +/** + * The largest finite float64 value. + * @const {number} + */ +const FLOAT64_MAX = 1.7976931348623157e+308; + + +/** + * Convenience constant equal to 2^20. + * @const {number} + */ +const TWO_TO_20 = 1048576; + + +/** + * Convenience constant equal to 2^23. + * @const {number} + */ +const TWO_TO_23 = 8388608; + + +/** + * Convenience constant equal to 2^31. + * @const {number} + */ +const TWO_TO_31 = 2147483648; + + +/** + * Convenience constant equal to 2^32. + * @const {number} + */ +const TWO_TO_32 = 4294967296; + + +/** + * Convenience constant equal to 2^52. + * @const {number} + */ +const TWO_TO_52 = 4503599627370496; + + +/** + * Convenience constant equal to 2^63. + * @const {number} + */ +const TWO_TO_63 = 9223372036854775808; + + +/** + * Convenience constant equal to 2^64. + * @const {number} + */ +const TWO_TO_64 = 18446744073709551616; + + +/** + * Eight-character string of zeros, used as the default 64-bit hash value. + * @const {string} + */ +const ZERO_HASH = '\0\0\0\0\0\0\0\0'; + + +// See MessageSet wire format: https://github.com/protocolbuffers/protobuf/blob/735621f7d720fcacc0a05114f407c3817411f296/src/google/protobuf/descriptor.proto#L600 +// message MessageSet { +// repeated group Item = 1 { +// required uint32 type_id = 2; +// required bytes message = 3; +// }; +// }; +const /** number */ MESSAGE_SET_GROUP_NUMBER = 1; +const /** number */ MESSAGE_SET_TYPE_ID_FIELD_NUMBER = 2; +const /** number */ MESSAGE_SET_MESSAGE_FIELD_NUMBER = 3; +const /** number */ MESSAGE_SET_MAX_TYPE_ID = 0xFFFFFFFE; + +exports = { + FieldType, + FieldTypeToWireType, + FLOAT32_EPS, + FLOAT32_MIN, + FLOAT32_MAX, + FLOAT64_EPS, + FLOAT64_MIN, + FLOAT64_MAX, + INVALID_FIELD_NUMBER, + INVALID_TAG, + MESSAGE_SET_GROUP_NUMBER, + MESSAGE_SET_MAX_TYPE_ID, + MESSAGE_SET_MESSAGE_FIELD_NUMBER, + MESSAGE_SET_TYPE_ID_FIELD_NUMBER, + TWO_TO_20, + TWO_TO_23, + TWO_TO_31, + TWO_TO_32, + TWO_TO_52, + TWO_TO_63, + TWO_TO_64, + WireType, + ZERO_HASH, + isValidWireType, +}; diff --git a/binary/bytesource.js b/binary/bytesource.js new file mode 100755 index 0000000..db009d5 --- /dev/null +++ b/binary/bytesource.js @@ -0,0 +1,10 @@ +goog.module('jspb.binary.bytesource'); + +/** + * The types convertible to Uint8Arrays. Strings are assumed to be + * base64-encoded. + * @typedef {?ArrayBuffer|?Uint8Array|?ReadonlyArray|string} + */ +let ByteSource; + +exports = {ByteSource}; diff --git a/binary/constants.js b/binary/constants.js deleted file mode 100644 index 600b26c..0000000 --- a/binary/constants.js +++ /dev/null @@ -1,409 +0,0 @@ -// Protocol Buffers - Google's data interchange format -// Copyright 2008 Google Inc. All rights reserved. -// https://protobuf.dev/ -// -// 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 This file contains constants and typedefs used by - * jspb.BinaryReader and BinaryWriter. - * @suppress {missingRequire} TODO(b/152540451): this shouldn't be needed - * - * @author aappleby@google.com (Austin Appleby) - */ - -goog.provide('jspb.AnyFieldType'); -goog.provide('jspb.BinaryConstants'); -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.BinaryMessage'); -goog.forwardDeclare('jspb.BinaryReader'); -goog.forwardDeclare('jspb.BinaryWriter'); -goog.forwardDeclare('jspb.Message'); -goog.forwardDeclare('jsprotolib.BinaryExtension'); - - - -/** - * Base interface class for all const messages. - * @interface - * @export - */ -jspb.ConstBinaryMessage = function() {}; - -/** - * Generate a debug string for this proto that is in proto2 text format. - * @return {string} The debug string. - * @export - */ -jspb.ConstBinaryMessage.prototype.toDebugString; - -/** - * Helper to generate a debug string for this proto at some indent level. The - * first line is not indented. - * @param {number} indentLevel The number of spaces by which to indent lines. - * @return {string} The debug string. - * @protected - */ -jspb.ConstBinaryMessage.prototype.toDebugStringInternal; - -/** - * Base interface class for all messages. Does __not__ define any methods, as - * doing so on a widely-used interface defeats dead-code elimination. - * @interface - * @extends {jspb.ConstBinaryMessage} - * @export - */ -jspb.BinaryMessage = function() {}; - - -/** - * The types convertible to Uint8Arrays. Strings are assumed to be - * base64-encoded. - * @typedef {ArrayBuffer|Uint8Array|Array|string} - * @export - */ -jspb.ByteSource; - - -/** - * A scalar field in jspb can be a boolean, number, or string. - * @typedef {boolean|number|string} - * @export - */ -jspb.ScalarFieldType; - - -/** - * A repeated field in jspb is an array of scalars, blobs, or messages. - * @typedef {!Array| - !Array| - !Array| - !Array} -* @export - */ -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 {jspb.ScalarFieldType| - jspb.RepeatedFieldType| - !Uint8Array| - !jspb.ConstBinaryMessage| - !jspb.BinaryMessage| - !jsprotolib.BinaryExtension} - * @export - */ -jspb.AnyFieldType; - - -/** - * A builder function creates an instance of a message object. - * @typedef {function():!jspb.BinaryMessage} - * @export - */ -jspb.BuilderFunction; - - -/** - * A cloner function creates a deep copy of a message object. - * @typedef {function(jspb.ConstBinaryMessage):jspb.BinaryMessage} - * @export - */ -jspb.ClonerFunction; - - -/** - * A recycler function destroys an instance of a message object. - * @typedef {function(!jspb.BinaryMessage):void} - * @export - */ -jspb.RecyclerFunction; - - -/** - * A reader function initializes a message using data from a BinaryReader. - * @typedef {function(!jspb.BinaryMessage, !jspb.BinaryReader):void} - * @export - */ -jspb.ReaderFunction; - - -/** - * A writer function serializes a message to a BinaryWriter. - * @typedef {function((!jspb.Message|!jspb.ConstBinaryMessage), - * !jspb.BinaryWriter):void} - * @export - */ -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} - * @export - */ -jspb.PrunerFunction; - - -/** - * A comparer function returns true if two protos are equal. - * @typedef {function(?jspb.ConstBinaryMessage, - * ?jspb.ConstBinaryMessage):boolean} - * @export - */ -jspb.ComparerFunction; - - -/** - * Field type codes, taken from proto2/public/wire_format_lite.h. - * @enum {number} - * @export - */ -jspb.BinaryConstants.FieldType = { - INVALID: -1, - DOUBLE: 1, - FLOAT: 2, - INT64: 3, - UINT64: 4, - INT32: 5, - FIXED64: 6, - FIXED32: 7, - BOOL: 8, - STRING: 9, - GROUP: 10, - MESSAGE: 11, - BYTES: 12, - UINT32: 13, - ENUM: 14, - SFIXED32: 15, - SFIXED64: 16, - SINT32: 17, - SINT64: 18, - - // Extended types for Javascript - - FHASH64: 30, // 64-bit hash string, fixed-length encoding. - VHASH64: 31 // 64-bit hash string, varint encoding. -}; - - -/** - * Wire-format type codes, taken from proto2/public/wire_format_lite.h. - * @enum {number} - * @export - */ -jspb.BinaryConstants.WireType = { - INVALID: -1, - VARINT: 0, - FIXED64: 1, - DELIMITED: 2, - START_GROUP: 3, - END_GROUP: 4, - FIXED32: 5 -}; - - -/** - * Translates field type to wire type. - * @param {jspb.BinaryConstants.FieldType} fieldType - * @return {jspb.BinaryConstants.WireType} - * @export - */ -jspb.BinaryConstants.FieldTypeToWireType = function(fieldType) { - var fieldTypes = jspb.BinaryConstants.FieldType; - var wireTypes = jspb.BinaryConstants.WireType; - switch (fieldType) { - case fieldTypes.INT32: - case fieldTypes.INT64: - case fieldTypes.UINT32: - case fieldTypes.UINT64: - case fieldTypes.SINT32: - case fieldTypes.SINT64: - case fieldTypes.BOOL: - case fieldTypes.ENUM: - case fieldTypes.VHASH64: - return wireTypes.VARINT; - - case fieldTypes.DOUBLE: - case fieldTypes.FIXED64: - case fieldTypes.SFIXED64: - case fieldTypes.FHASH64: - return wireTypes.FIXED64; - - case fieldTypes.STRING: - case fieldTypes.MESSAGE: - case fieldTypes.BYTES: - return wireTypes.DELIMITED; - - case fieldTypes.FLOAT: - case fieldTypes.FIXED32: - case fieldTypes.SFIXED32: - return wireTypes.FIXED32; - - case fieldTypes.INVALID: - case fieldTypes.GROUP: - default: - return wireTypes.INVALID; - } -}; - - -/** - * Flag to indicate a missing field. - * @const {number} - * @export - */ -jspb.BinaryConstants.INVALID_FIELD_NUMBER = -1; - - -/** - * The smallest denormal float32 value. - * @const {number} - * @export - */ -jspb.BinaryConstants.FLOAT32_EPS = 1.401298464324817e-45; - - -/** - * The smallest normal float64 value. - * @const {number} - * @export - */ -jspb.BinaryConstants.FLOAT32_MIN = 1.1754943508222875e-38; - - -/** - * The largest finite float32 value. - * @const {number} - * @export - */ -jspb.BinaryConstants.FLOAT32_MAX = 3.4028234663852886e+38; - - -/** - * The smallest denormal float64 value. - * @const {number} - * @export - */ -jspb.BinaryConstants.FLOAT64_EPS = 5e-324; - - -/** - * The smallest normal float64 value. - * @const {number} - * @export - */ -jspb.BinaryConstants.FLOAT64_MIN = 2.2250738585072014e-308; - - -/** - * The largest finite float64 value. - * @const {number} - * @export - */ -jspb.BinaryConstants.FLOAT64_MAX = 1.7976931348623157e+308; - - -/** - * Convenience constant equal to 2^20. - * @const {number} - * @export - */ -jspb.BinaryConstants.TWO_TO_20 = 1048576; - - -/** - * Convenience constant equal to 2^23. - * @const {number} - * @export - */ -jspb.BinaryConstants.TWO_TO_23 = 8388608; - - -/** - * Convenience constant equal to 2^31. - * @const {number} - * @export - */ -jspb.BinaryConstants.TWO_TO_31 = 2147483648; - - -/** - * Convenience constant equal to 2^32. - * @const {number} - * @export - */ -jspb.BinaryConstants.TWO_TO_32 = 4294967296; - - -/** - * Convenience constant equal to 2^52. - * @const {number} - * @export - */ -jspb.BinaryConstants.TWO_TO_52 = 4503599627370496; - - -/** - * Convenience constant equal to 2^63. - * @const {number} - * @export - */ -jspb.BinaryConstants.TWO_TO_63 = 9223372036854775808; - - -/** - * Convenience constant equal to 2^64. - * @const {number} - * @export - */ -jspb.BinaryConstants.TWO_TO_64 = 18446744073709551616; - - -/** - * Eight-character string of zeros, used as the default 64-bit hash value. - * @const {string} - * @export - */ -jspb.BinaryConstants.ZERO_HASH = '\0\0\0\0\0\0\0\0'; diff --git a/binary/decoder.js b/binary/decoder.js old mode 100644 new mode 100755 index 5dac0dd..c39cae9 --- a/binary/decoder.js +++ b/binary/decoder.js @@ -1,33 +1,3 @@ -// Protocol Buffers - Google's data interchange format -// Copyright 2008 Google Inc. All rights reserved. -// https://protobuf.dev/ -// -// 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 This file contains utilities for decoding primitive values * (signed and unsigned integers, varints, booleans, enums, hashes, strings, @@ -37,931 +7,1109 @@ * Major caveat - Javascript is unable to accurately represent integers larger * than 2^53 due to its use of a double-precision floating point format or all * numbers. If you need to guarantee that 64-bit values survive with all bits - * intact, you _must_ read them using one of the Hash64 methods, which return - * an 8-character string. + * intact, you _must_ read them using one of the split methods, which return + * numbers. * - * @suppress {missingRequire} TODO(b/152540451): this shouldn't be needed * @author aappleby@google.com (Austin Appleby) */ -goog.provide('jspb.BinaryDecoder'); - -goog.require('jspb.asserts'); -goog.require('jspb.binary.utf8'); -goog.require('jspb.utils'); +goog.module('jspb.binary.decoder'); +goog.module.declareLegacyNamespace(); +const asserts = goog.require('goog.asserts'); +const errors = goog.require('jspb.binary.errors'); +const utils = goog.require('jspb.utils'); +const {BinaryReaderOptions} = goog.requireType('jspb.binary.reader'); +const {Buffer, bufferFromSource} = goog.require('jspb.binary.internal_buffer'); +const {ByteSource} = goog.require('jspb.binary.bytesource'); +const {ByteString} = goog.require('jspb.bytestring'); +const {decodeUtf8} = goog.require('jspb.binary.utf8'); +const {unsafeByteStringFromUint8Array} = goog.require('jspb.unsafe_bytestring'); /** - * BinaryDecoder implements the decoders for all the wire types specified in - * https://protobuf.dev/programming-guides/encoding/. + * The maximum number of bytes in a varint. * - * @param {jspb.ByteSource=} opt_bytes The bytes we're reading from. - * @param {number=} opt_start The optional offset to start reading at. - * @param {number=} opt_length The optional length of the block to read - - * we'll throw an assertion if we go off the end of the block. - * @constructor - * @struct - * @export + * Every byte in a varint provides 7 bits and the largest number requires 64 + * bits which implies it would take up to 10 bytes to provide all the bits. `10 + * == Math.ceil(64/7)` */ -jspb.BinaryDecoder = function(opt_bytes, opt_start, opt_length) { - /** - * Typed byte-wise view of the source buffer. - * @private {?Uint8Array} - */ - this.bytes_ = null; +const /** number */ MAX_VARINT_SIZE = 10; +class BinaryDecoder { /** - * Start point of the block to read. - * @private {number} + * BinaryDecoder implements the decoders for all the wire types specified in + * https://developers.google.com/protocol-buffers/docs/encoding. + * + * @param {?ByteSource|!ByteString=} bytes The bytes we're reading from. + * @param {number=} start The optional offset to start reading at. + * @param {number=} length The optional length of the block to read - + * we'll throw an assertion if we go off the end of the block. + * @param {!BinaryReaderOptions=} options options for this decoder. */ - this.start_ = 0; + constructor(bytes, start, length, options) { + /** + * Typed byte-wise view of the source buffer. + * @private {?Uint8Array} + */ + this.bytes_ = null; - /** - * End point of the block to read. - * @private {number} - */ - this.end_ = 0; + /** + * Typed byte-wise view of the source buffer, if the data is immutable. + * @private {?Buffer} + */ + this.buffer_ = null; - /** - * Current read location in bytes_. - * @private {number} - */ - this.cursor_ = 0; + /** @private {boolean} */ + this.bytesAreImmutable_ = false; - /** - * Set to true if this decoder encountered an error due to corrupt data. - * @private {boolean} - */ - this.error_ = false; + if (ASSUME_DATAVIEW_IS_FAST) { + /** + * DataView of the source buffer. + * @private {?DataView} + */ + this.dataView_ = null; + } - if (opt_bytes) { - this.setBlock(opt_bytes, opt_start, opt_length); - } -}; + /** + * Start point of the block to read. + * @private {number} + */ + this.start_ = 0; + /** + * End point of the block to read, this is exclusive. + * @private {number} + */ + this.end_ = 0; -/** - * Global pool of BinaryDecoder instances. - * @private {!Array} - */ -jspb.BinaryDecoder.instanceCache_ = []; + /** + * Current read location in bytes_. + * @private {number} + */ + this.cursor_ = 0; + /** + * Set to true if this decoder should use subarray instead of slice. + * @private {boolean} + */ + this.aliasBytesFields; -/** - * @return {number} - * @export - */ -jspb.BinaryDecoder.getInstanceCacheLength = function() { - return jspb.BinaryDecoder.instanceCache_.length; -} + /** + * Whether we should treat newly deserialized data as being immutable. + * + * @private {boolean} + */ + this.treatNewDataAsImmutable; -/** - * Pops an instance off the instance cache, or creates one if the cache is - * empty. - * @param {jspb.ByteSource=} opt_bytes The bytes we're reading from. - * @param {number=} opt_start The optional offset to start reading at. - * @param {number=} opt_length The optional length of the block to read - - * we'll throw an assertion if we go off the end of the block. - * @return {!jspb.BinaryDecoder} - * @export - */ -jspb.BinaryDecoder.alloc = function(opt_bytes, opt_start, opt_length) { - if (jspb.BinaryDecoder.instanceCache_.length) { - var newDecoder = jspb.BinaryDecoder.instanceCache_.pop(); - if (opt_bytes) { - newDecoder.setBlock(opt_bytes, opt_start, opt_length); - } - return newDecoder; - } else { - return new jspb.BinaryDecoder(opt_bytes, opt_start, opt_length); + this.init(bytes, start, length, options); } -}; - -/** - * Puts this instance back in the instance cache. - * @export - */ -jspb.BinaryDecoder.prototype.free = function() { - this.clear(); - if (jspb.BinaryDecoder.instanceCache_.length < 100) { - jspb.BinaryDecoder.instanceCache_.push(this); + /** + * @param {?ByteSource|!ByteString=} bytes The bytes we're reading from. + * @param {number=} start The optional offset to start reading at. + * @param {number=} length The optional length of the block to read - + * we'll throw an assertion if we go off the end of the block. + * @param {!BinaryReaderOptions=} options options for this decoder. + * @private + */ + init( + bytes, start, length, + { aliasBytesFields = false, treatNewDataAsImmutable = false } = {}) { + this.aliasBytesFields = aliasBytesFields; + this.treatNewDataAsImmutable = treatNewDataAsImmutable; + if (bytes) { + this.setBlock(bytes, start, length); + } } -}; - - -/** - * Makes a copy of this decoder. - * @return {!jspb.BinaryDecoder} - * @export - */ -jspb.BinaryDecoder.prototype.clone = function() { - return jspb.BinaryDecoder.alloc( - this.bytes_, this.start_, this.end_ - this.start_); -}; - - -/** - * Clears the decoder. - * @export - */ -jspb.BinaryDecoder.prototype.clear = function() { - this.bytes_ = null; - this.start_ = 0; - this.end_ = 0; - this.cursor_ = 0; - this.error_ = false; -}; - -/** - * Returns the raw buffer. - * @return {?Uint8Array} The raw buffer. - * @export - */ -jspb.BinaryDecoder.prototype.getBuffer = function() { - return this.bytes_; -}; + /** + * Pops an instance off the instance cache, or creates one if the cache is + * empty. + * @param {?ByteSource|!ByteString=} bytes The bytes we're reading from. + * @param {number=} start The optional offset to start reading at. + * @param {number=} length The optional length of the block to read - + * we'll throw an assertion if we go off the end of the block. + * @param {!BinaryReaderOptions=} options options for this decoder. + * @return {!BinaryDecoder} + * @export + */ + static alloc(bytes, start, length, options) { + if (BinaryDecoder.instanceCache_.length) { + const newDecoder = BinaryDecoder.instanceCache_.pop(); + newDecoder.init(bytes, start, length, options); + return newDecoder; + } else { + return new BinaryDecoder(bytes, start, length, options); + } + } + /** + * Puts this instance back in the instance cache. + * @export + */ + free() { + this.clear(); + if (BinaryDecoder.instanceCache_.length < 100) { + BinaryDecoder.instanceCache_.push(this); + } + } -/** - * Changes the block of bytes we're decoding. - * @param {!jspb.ByteSource} data The bytes we're reading from. - * @param {number=} opt_start The optional offset to start reading at. - * @param {number=} opt_length The optional length of the block to read - - * we'll throw an assertion if we go off the end of the block. - * @export - */ -jspb.BinaryDecoder.prototype.setBlock = function(data, opt_start, opt_length) { - this.bytes_ = jspb.utils.byteSourceToUint8Array(data); - this.start_ = (opt_start !== undefined) ? opt_start : 0; - this.end_ = (opt_length !== undefined) ? this.start_ + opt_length : - this.bytes_.length; - this.cursor_ = this.start_; -}; + /** + * Clears the decoder. + * @export + */ + clear() { + this.bytes_ = null; + this.buffer_ = null; + this.bytesAreImmutable_ = false; + if (ASSUME_DATAVIEW_IS_FAST) { + this.dataView_ = null; + } + this.start_ = 0; + this.end_ = 0; + this.cursor_ = 0; + this.aliasBytesFields = false; + } + /** + * @export + * @return {boolean} + */ + dataIsImmutable() { + return this.bytesAreImmutable_; + } -/** - * @return {number} - * @export - */ -jspb.BinaryDecoder.prototype.getEnd = function() { - return this.end_; -}; + /** + * Returns the raw buffer. + * + * Throws if the internal buffer is immutable + * + * @export + * @return {?Uint8Array} The raw buffer. + */ + getBuffer() { + if (this.bytesAreImmutable_) { + throw goog.DEBUG ? + new Error( + 'cannot access the buffer of decoders over immutable data.') : + new Error(); + } + return this.bytes_; + } + /** + * Returns the raw buffer. + * + * Throws if the internal buffer is mutable + * + * @export + * @return {?ByteString} The buffer. + */ + getBufferAsByteString() { + if (this.buffer_ == null) return null; + if (!this.bytesAreImmutable_) { + throw goog.DEBUG ? + new Error( + 'cannot access the buffer of decoders over immutable data.') : + new Error(); + } + return this.buffer_.getBufferAsByteStringIfImmutable(); + } -/** - * @param {number} end - * @export - */ -jspb.BinaryDecoder.prototype.setEnd = function(end) { - this.end_ = end; -}; + /** + * Changes the block of bytes we're decoding. + * @param {!ByteSource|!ByteString} data The bytes we're reading from. + * @param {number=} start The optional offset to start reading at. + * @param {number=} length The optional length of the block to read - + * we'll throw an assertion if we go off the end of the block. + * + * @export + */ + setBlock(data, start, length) { + const unpackedData = bufferFromSource(data, this.treatNewDataAsImmutable); + this.buffer_ = unpackedData; + this.bytes_ = unpackedData.buffer; + this.bytesAreImmutable_ = unpackedData.isImmutable; + if (ASSUME_DATAVIEW_IS_FAST) this.dataView_ = null; + this.start_ = start || 0; + this.end_ = + (length !== undefined) ? this.start_ + length : this.bytes_.length; + this.cursor_ = this.start_; + } + /** + * @export + * @return {number} + */ + getEnd() { + return this.end_; + } -/** - * Moves the read cursor back to the start of the block. - * @export - */ -jspb.BinaryDecoder.prototype.reset = function() { - this.cursor_ = this.start_; -}; + /** + * @param {number} end + * @export + */ + setEnd(end) { + this.end_ = end; + } -/** - * Returns the internal read cursor. - * @return {number} The internal read cursor. - * @export - */ -jspb.BinaryDecoder.prototype.getCursor = function() { - return this.cursor_; -}; + /** + * Moves the read cursor back to the start of the block. + * @export + */ + reset() { + this.cursor_ = this.start_; + } -/** - * Returns the internal read cursor. - * @param {number} cursor The new cursor. - * @export - */ -jspb.BinaryDecoder.prototype.setCursor = function(cursor) { - this.cursor_ = cursor; -}; + /** + * Returns the internal read cursor. + * @export + * @return {number} The internal read cursor. + */ + getCursor() { + return this.cursor_; + } + /** + * Returns the internal read cursor. + * @param {number} cursor The new cursor. + * @export + */ + setCursor(cursor) { + this.cursor_ = cursor; + } -/** - * Advances the stream cursor by the given number of bytes. - * @param {number} count The number of bytes to advance by. - * @export - */ -jspb.BinaryDecoder.prototype.advance = function(count) { - this.cursor_ += count; - this.checkCursor(); -}; + /** + * Advances the stream cursor by the given number of bytes. + * @param {number} count The number of bytes to advance by. + * @export + */ + advance(count) { + const newCursor = this.cursor_ + count; + this.setCursorAndCheck(newCursor); + } -/** - * Returns true if this decoder is at the end of the block. - * @return {boolean} - * @export - */ -jspb.BinaryDecoder.prototype.atEnd = function() { - return this.cursor_ == this.end_; -}; + /** + * Returns true if this decoder is at the end of the block. + * @return {boolean} + * @export + */ + atEnd() { + return this.cursor_ == this.end_; + } -/** - * Returns true if this decoder is at the end of the block. - * @return {boolean} - * @export - */ -jspb.BinaryDecoder.prototype.pastEnd = function() { - return this.cursor_ > this.end_; -}; + /** + * Returns true if this decoder is at the end of the block. + * @return {boolean} + * @export + */ + pastEnd() { + return this.cursor_ > this.end_; + } + /** + * Reads an unsigned 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. + * + * Decoding varints requires doing some funny base-128 math - for more + * details on the format, see + * https://developers.google.com/protocol-buffers/docs/encoding + * + * @param {!BinaryDecoder} decoder + * @param {function(number, number): T} convert Conversion function to produce + * the result value, takes parameters (lowBits, highBits). + * @return {T} + * @export + * @template T + */ + static readSplitVarint64(decoder, convert) { + let temp = 0; + let lowBits = 0; + let highBits = 0; + let shift = 0; + const bytes = decoder.bytes_; + let cursor = decoder.cursor_; + + // Read the first five bytes of the varint, stopping at the terminator if we + // see it. + do { + temp = bytes[cursor++]; + lowBits |= (temp & 0x7F) << shift; + shift += 7; + } while (shift < 32 && temp & 0x80); + + if (shift > 32) { + // The fifth byte was read, which straddles the low and high dwords, + // Save its contribution to the high dword. + highBits |= (temp & 0x7F) >> 4; + } -/** - * Returns true if this decoder encountered an error due to corrupt data. - * @return {boolean} - * @export - */ -jspb.BinaryDecoder.prototype.getError = function() { - return this.error_ || (this.cursor_ < 0) || (this.cursor_ > this.end_); -}; + // Read the sixth through tenth byte. + for (shift = 3; shift < 32 && temp & 0x80; shift += 7) { + temp = bytes[cursor++]; + highBits |= (temp & 0x7F) << shift; + } + decoder.setCursorAndCheck(cursor); -/** - * Reads an unsigned 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. - * - * Decoding varints requires doing some funny base-128 math - for more - * details on the format, see - * https://protobuf.dev/programming-guides/encoding/ - * - * @param {function(number, number): T} convert Conversion function to produce - * the result value, takes parameters (lowBits, highBits). - * @return {T} - * @template T - * @export - */ -jspb.BinaryDecoder.prototype.readSplitVarint64 = function(convert) { - var temp = 128; - var lowBits = 0; - var highBits = 0; + if (temp < 128) { + return convert(lowBits >>> 0, highBits >>> 0); + } - // Read the first four bytes of the varint, stopping at the terminator if we - // see it. - for (var i = 0; i < 4 && temp >= 128; i++) { - temp = this.bytes_[this.cursor_++]; - lowBits |= (temp & 0x7F) << (i * 7); + // If we did not see the terminator, the encoding was invalid. + throw errors.invalidVarintError(); } - if (temp >= 128) { - // Read the fifth byte, which straddles the low and high dwords. - temp = this.bytes_[this.cursor_++]; - lowBits |= (temp & 0x7F) << 28; - highBits |= (temp & 0x7F) >> 4; + /** + * 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 {!BinaryDecoder} decoder + * @param {function(number, number): T} convert Conversion function to produce + * the result value, takes parameters (lowBits, highBits). + * @return {T} + * @export + * @template T + */ + static readSplitZigzagVarint64(decoder, convert) { + return BinaryDecoder.readSplitVarint64( + decoder, (low, high) => utils.fromZigzag64(low, high, convert)); } - if (temp >= 128) { - // Read the sixth through tenth byte. - for (var i = 0; i < 5 && temp >= 128; i++) { - temp = this.bytes_[this.cursor_++]; - highBits |= (temp & 0x7F) << (i * 7 + 3); + /** + * 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. + * + * @param {!BinaryDecoder} decoder + * @param {function(number, number): T} convert Conversion function to produce + * the result value, takes parameters (lowBits, highBits). + * @return {T} + * @export + * @template T + */ + static readSplitFixed64(decoder, convert) { + const bytes = decoder.bytes_; + const cursor = decoder.cursor_; + decoder.advance(8); + let lowBits = 0; + let highBits = 0; + for (let i = cursor + 7; i >= cursor; i--) { + lowBits = (lowBits << 8) | bytes[i]; + highBits = (highBits << 8) | bytes[i + 4]; } + return convert(lowBits, highBits); } - if (temp < 128) { - return convert(lowBits >>> 0, highBits >>> 0); + /** + * Skips over a varint in the block without decoding it. + * @export + */ + skipVarint() { + // readBool does only trivial decoding, delegate to it. + BinaryDecoder.readBool(this); } - // If we did not see the terminator, the encoding was invalid. - jspb.asserts.fail('Failed to read varint, encoding is invalid.'); - this.error_ = true; -}; - - -/** - * 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://protobuf.dev/programming-guides/encoding/ - * - * @param {function(number, number): T} convert Conversion function to produce - * the result value, takes parameters (lowBits, highBits). - * @return {T} - * @template T - * @export - */ -jspb.BinaryDecoder.prototype.readSplitZigzagVarint64 = function(convert) { - return this.readSplitVarint64(function(low, high) { - return jspb.utils.fromZigzag64(low, high, convert); - }); -}; - + /** + * Asserts that our cursor is in bounds. + * + * @private + * @param {number} cursor + * @return {void} + */ + setCursorAndCheck(cursor) { + this.cursor_ = cursor; + if (cursor > this.end_) { + throw errors.readTooFarError(this.end_, cursor); + } + } -/** - * 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. - * - * @param {function(number, number): T} convert Conversion function to produce - * the result value, takes parameters (lowBits, highBits). - * @return {T} - * @template T - * @export - */ -jspb.BinaryDecoder.prototype.readSplitFixed64 = function(convert) { - var bytes = this.bytes_; - var cursor = this.cursor_; - this.cursor_ += 8; - var lowBits = 0; - var highBits = 0; - for (var i = cursor + 7; i >= cursor; i--) { - lowBits = (lowBits << 8) | bytes[i]; - highBits = (highBits << 8) | bytes[i + 4]; - } - return convert(lowBits, highBits); -}; + /** + * Reads a 32-bit varint from the binary stream. + * + * This function is called vastly more frequently than any other in + * BinaryDecoder, so it has been unrolled and tweaked for performance. + * + * Decoding varints requires doing some funny base-128 math - for more + * details on the format, see + * https://developers.google.com/protocol-buffers/docs/encoding + * + * @param {!BinaryDecoder} decoder + * @return {number} The decoded unsigned 32-bit varint. + * @export + */ + static readSignedVarint32(decoder) { + const bytes = decoder.bytes_; + let cursor = decoder.cursor_; + + let temp = bytes[cursor++]; + let x = (temp & 0x7F); + + if (temp & 0x80) { + temp = bytes[cursor++]; + x |= (temp & 0x7F) << 7; + + if (temp & 0x80) { + temp = bytes[cursor++]; + x |= (temp & 0x7F) << 14; + + if (temp & 0x80) { + temp = bytes[cursor++]; + x |= (temp & 0x7F) << 21; + + if (temp & 0x80) { + temp = bytes[cursor++]; + // We're reading the high bits of an unsigned varint. The byte we + // just read also contains bits 33 through 35, which we're going to + // discard. + x |= temp << 28; + + if (temp & 0x80) { + // If we get here, we need to truncate coming bytes. However we + // need to make sure cursor place is correct. + if (bytes[cursor++] & 0x80 && bytes[cursor++] & 0x80 && + bytes[cursor++] & 0x80 && bytes[cursor++] & 0x80 && + bytes[cursor++] & 0x80) { + // If we get here, the varint is too long. + throw errors.invalidVarintError(); + } + } + } + } + } + } -/** - * Asserts that our cursor is in bounds. - * - * @private - * @return {void} - */ -jspb.BinaryDecoder.prototype.checkCursor = function () { - if (this.cursor_ > this.end_) { - jspb.asserts.fail('Read past the end ' + this.cursor_ + ' > ' + this.end_); + decoder.setCursorAndCheck(cursor); + return x; } -} -/** - * Skips over a varint in the block without decoding it. - * @export - */ -jspb.BinaryDecoder.prototype.skipVarint = function() { - while (this.bytes_[this.cursor_] & 0x80) { - this.cursor_++; + /** + * Reads an unsigned 32-bit varint from the binary stream. + * + * @param {!BinaryDecoder} decoder + * @return {number} The decoded signed 32-bit varint. + * @export + */ + static readUnsignedVarint32(decoder) { + return BinaryDecoder.readSignedVarint32(decoder) >>> 0; } - this.cursor_++; -}; - -/** - * Skips backwards over a varint in the block - to do this correctly, we have - * to know the value we're skipping backwards over or things are ambiguous. - * @param {number} value The varint value to unskip. - * @export - */ -jspb.BinaryDecoder.prototype.unskipVarint = function(value) { - while (value > 128) { - this.cursor_--; - value = value >>> 7; + /** + * Peeks into the binary stream. If the value is an unsigned varint matching + * `expected`, returns the initial cursor position and advances the cursor + * past the varint. If the value is different, or the stream ends, returns + * -1. + * + * Designed for peeking to see the field header tag for the next field. + * + * Will only match minimal encodings, will only read up to 35 bits (5 7-bit + * bytes), and will not match over-long encodings even if they are short. + * + * A) These methods are optional optimizations, if they're conservative in + * matching, nothing should break, we'll just see another repeated field the + * normal way if the tag is encoded strangely. + * + * B) We don't want to reuse the "normal" read method, because it will throw + * on invalid formats and reading past the end, which we don't want to fail. + * + * C) If we somehow start to expect a difference in varint encoding, this + * decoder method likely needs to be aware of what information is encoded in + * that deviation anyway. + * + * @param {number} expected + * @return {number} + * @export + */ + readUnsignedVarint32IfEqualTo(expected) { + asserts.assert(expected === expected >>> 0); + const initialCursor = this.cursor_; + let cursor = initialCursor; + const end = this.end_; + const bytes = this.bytes_; + while (cursor < end) { + if (expected > 0x7F) { + const expectedByte = 0x80 | (expected & 0x7F); + if (bytes[cursor++] !== expectedByte) { + return -1; + } + expected >>>= 7; + } else { + // Last byte of the expected value. + if (bytes[cursor++] === expected) { + this.cursor_ = cursor; + return initialCursor; + } else { + return -1; + } + } + } + return -1; } - this.cursor_--; -}; -/** - * Reads a 32-bit varint from the binary stream. Due to a quirk of the encoding - * format and Javascript's handling of bitwise math, this actually works - * correctly for both signed and unsigned 32-bit varints. - * - * This function is called vastly more frequently than any other in - * BinaryDecoder, so it has been unrolled and tweaked for performance. - * - * If there are more than 32 bits of data in the varint, it _must_ be due to - * sign-extension. If we're in debug mode and the high 32 bits don't match the - * expected sign extension, this method will throw an error. - * - * Decoding varints requires doing some funny base-128 math - for more - * details on the format, see - * https://protobuf.dev/programming-guides/encoding/ - * - * @return {number} The decoded unsigned 32-bit varint. - * @export - */ -jspb.BinaryDecoder.prototype.readUnsignedVarint32 = function() { - var temp; - var bytes = this.bytes_; - - temp = bytes[this.cursor_ + 0]; - var x = (temp & 0x7F); - if (temp < 128) { - this.cursor_ += 1; - this.checkCursor(); - return x; + /** + * Reads a signed, zigzag-encoded 32-bit varint from the binary stream. + * + * 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 {!BinaryDecoder} decoder + * @return {number} The decoded signed, zigzag-encoded 32-bit varint. + * @export + */ + static readZigzagVarint32(decoder) { + return utils.fromZigzag32(BinaryDecoder.readUnsignedVarint32(decoder)); } - temp = bytes[this.cursor_ + 1]; - x |= (temp & 0x7F) << 7; - if (temp < 128) { - this.cursor_ += 2; - this.checkCursor(); - return x; + /** + * Reads an unsigned 64-bit varint from the binary stream. Note that since + * Javascript represents all numbers as double-precision floats, there will be + * precision lost if the absolute value of the varint is larger than 2^53. + * + * @param {!BinaryDecoder} decoder + * @return {number} The decoded unsigned varint. Precision will be lost if the + * integer exceeds 2^53. + * @export + */ + static readUnsignedVarint64(decoder) { + return BinaryDecoder.readSplitVarint64(decoder, utils.joinUint64); } - temp = bytes[this.cursor_ + 2]; - x |= (temp & 0x7F) << 14; - if (temp < 128) { - this.cursor_ += 3; - this.checkCursor(); - return x; + /** + * Reads an unsigned 64-bit varint from the binary stream and returns the + * value as a decimal string. + * + * @param {!BinaryDecoder} decoder + * @return {string} The decoded unsigned varint as a decimal string. + * @export + */ + static readUnsignedVarint64String(decoder) { + return BinaryDecoder.readSplitVarint64( + decoder, utils.joinUnsignedDecimalString); } - temp = bytes[this.cursor_ + 3]; - x |= (temp & 0x7F) << 21; - if (temp < 128) { - this.cursor_ += 4; - this.checkCursor(); - return x; + /** + * Reads a signed 64-bit varint from the binary stream. Note that since + * Javascript represents all numbers as double-precision floats, there will be + * precision lost if the absolute value of the varint is larger than 2^53. + * + * @param {!BinaryDecoder} decoder + * @return {number} The decoded signed varint. Precision will be lost if the + * integer exceeds 2^53. + * @export + */ + static readSignedVarint64(decoder) { + return BinaryDecoder.readSplitVarint64(decoder, utils.joinInt64); } - temp = bytes[this.cursor_ + 4]; - x |= (temp & 0x0F) << 28; - if (temp < 128) { - // We're reading the high bits of an unsigned varint. The byte we just read - // also contains bits 33 through 35, which we're going to discard. - this.cursor_ += 5; - this.checkCursor(); - return x >>> 0; + /** + * Reads an signed 64-bit varint from the binary stream and returns the value + * as a decimal string. + * + * @param {!BinaryDecoder} decoder + * @return {string} The decoded signed varint as a decimal string. + * @export + */ + static readSignedVarint64String(decoder) { + return BinaryDecoder.readSplitVarint64( + decoder, utils.joinSignedDecimalString); } - // If we get here, we need to truncate coming bytes. However we need to make - // sure cursor place is correct. - this.cursor_ += 5; - if (bytes[this.cursor_++] >= 128 && bytes[this.cursor_++] >= 128 && - bytes[this.cursor_++] >= 128 && bytes[this.cursor_++] >= 128 && - bytes[this.cursor_++] >= 128) { - // If we get here, the varint is too long. - jspb.asserts.assert(false); + /** + * Reads a signed, zigzag-encoded 64-bit varint from the binary stream. Note + * that since Javascript represents all numbers as double-precision floats, + * there will be precision lost if the absolute value of the varint is larger + * than 2^53. + * + * 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 {!BinaryDecoder} decoder + * @return {number} The decoded zigzag varint. Precision will be lost if the + * integer exceeds 2^53. + * @export + */ + static readZigzagVarint64(decoder) { + return BinaryDecoder.readSplitVarint64(decoder, utils.joinZigzag64); } - this.checkCursor(); - return x; -}; - - -/** - * Coerces the output of readUnsignedVarint32 to an int32. - * - * @return {number} The decoded signed 32-bit varint. - * @export - */ -jspb.BinaryDecoder.prototype.readSignedVarint32 = - function() { - // The `~` operator coerces to int32, and `~~` is the shortest expression of a - // cast. This has some edge cases (e.g. NaN becomes 0) but should be okay - // here. - return ~~(this.readUnsignedVarint32()); -} - - - /** - * Reads a 32-bit unsigned variant and returns its value as a string. - * - * @return {string} The decoded unsigned 32-bit varint as a string. - */ - jspb.BinaryDecoder.prototype.readUnsignedVarint32String = function() { - // 32-bit integers fit in JavaScript numbers without loss of precision, so - // string variants of 32-bit varint readers can simply delegate then convert - // to string. - var value = this.readUnsignedVarint32(); - return value.toString(); -}; - - -/** - * Reads a 32-bit signed variant and returns its value as a string. - * - * @return {string} The decoded signed 32-bit varint as a string. - * @export - */ -jspb.BinaryDecoder.prototype.readSignedVarint32String = function() { - // 32-bit integers fit in JavaScript numbers without loss of precision, so - // string variants of 32-bit varint readers can simply delegate then convert - // to string. - var value = this.readSignedVarint32(); - return value.toString(); -}; - - -/** - * Reads a signed, zigzag-encoded 32-bit varint from the binary stream. - * - * 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://protobuf.dev/programming-guides/encoding/ - * - * @return {number} The decoded signed, zigzag-encoded 32-bit varint. - * @export - */ -jspb.BinaryDecoder.prototype.readZigzagVarint32 = function() { - var result = this.readUnsignedVarint32(); - return (result >>> 1) ^ -(result & 1); -}; - - -/** - * Reads an unsigned 64-bit varint from the binary stream. Note that since - * Javascript represents all numbers as double-precision floats, there will be - * precision lost if the absolute value of the varint is larger than 2^53. - * - * @return {number} The decoded unsigned varint. Precision will be lost if the - * integer exceeds 2^53. - * @export - */ -jspb.BinaryDecoder.prototype.readUnsignedVarint64 = function() { - return this.readSplitVarint64(jspb.utils.joinUint64); -}; - - -/** - * Reads an unsigned 64-bit varint from the binary stream and returns the value - * as a decimal string. - * - * @return {string} The decoded unsigned varint as a decimal string. - * @export - */ -jspb.BinaryDecoder.prototype.readUnsignedVarint64String = function() { - return this.readSplitVarint64(jspb.utils.joinUnsignedDecimalString); -}; - - -/** - * Reads a signed 64-bit varint from the binary stream. Note that since - * Javascript represents all numbers as double-precision floats, there will be - * precision lost if the absolute value of the varint is larger than 2^53. - * - * @return {number} The decoded signed varint. Precision will be lost if the - * integer exceeds 2^53. - * @export - */ -jspb.BinaryDecoder.prototype.readSignedVarint64 = function() { - return this.readSplitVarint64(jspb.utils.joinInt64); -}; - - -/** - * Reads an signed 64-bit varint from the binary stream and returns the value - * as a decimal string. - * - * @return {string} The decoded signed varint as a decimal string. - * @export - */ -jspb.BinaryDecoder.prototype.readSignedVarint64String = function() { - return this.readSplitVarint64(jspb.utils.joinSignedDecimalString); -}; - - -/** - * Reads a signed, zigzag-encoded 64-bit varint from the binary stream. Note - * that since Javascript represents all numbers as double-precision floats, - * there will be precision lost if the absolute value of the varint is larger - * than 2^53. - * - * 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://protobuf.dev/programming-guides/encoding/ - * - * @return {number} The decoded zigzag varint. Precision will be lost if the - * integer exceeds 2^53. - * @export - */ -jspb.BinaryDecoder.prototype.readZigzagVarint64 = function() { - return this.readSplitVarint64(jspb.utils.joinZigzag64); -}; - -/** - * 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://protobuf.dev/programming-guides/encoding/ - * - * @return {string} The decoded zigzag varint in hash64 format. - * @export - */ -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 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 + * format, see https://developers.google.com/protocol-buffers/docs/encoding + * + * @param {!BinaryDecoder} decoder + * @return {string} The decoded signed, zigzag-encoded 64-bit varint as a + * string. + * @export + */ + static readZigzagVarint64String(decoder) { + return BinaryDecoder.readSplitZigzagVarint64( + decoder, utils.joinSignedDecimalString); + } + /** + * Reads a raw unsigned 8-bit integer from the binary stream. + * + * @param {!BinaryDecoder} decoder + * @return {number} The unsigned 8-bit integer read from the binary stream. + * @export + */ + static readUint8(decoder) { + const a = decoder.bytes_[decoder.cursor_ + 0]; + decoder.advance(1); + return a; + } -/** - * Reads a signed, zigzag-encoded 64-bit varint from the binary stream and - * 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 - * format, see https://protobuf.dev/programming-guides/encoding/ - * - * @return {string} The decoded signed, zigzag-encoded 64-bit varint as a - * string. - * @export - */ -jspb.BinaryDecoder.prototype.readZigzagVarint64String = function() { - return this.readSplitZigzagVarint64(jspb.utils.joinSignedDecimalString); -}; + /** + * Reads a raw unsigned 16-bit integer from the binary stream. + * + * @param {!BinaryDecoder} decoder + * @return {number} The unsigned 16-bit integer read from the binary stream. + * @export + */ + static readUint16(decoder) { + const a = decoder.bytes_[decoder.cursor_ + 0]; + const b = decoder.bytes_[decoder.cursor_ + 1]; + decoder.advance(2); + return (a << 0) | (b << 8); + } -/** - * Reads a raw unsigned 8-bit integer from the binary stream. - * - * @return {number} The unsigned 8-bit integer read from the binary stream. - * @export - */ -jspb.BinaryDecoder.prototype.readUint8 = function() { - var a = this.bytes_[this.cursor_ + 0]; - this.cursor_ += 1; - this.checkCursor(); - return a; -}; + /** + * Reads a raw unsigned 32-bit integer from the binary stream. + * + * @param {!BinaryDecoder} decoder + * @return {number} The unsigned 32-bit integer read from the binary stream. + * @export + */ + static readUint32(decoder) { + const bytes = decoder.bytes_; + const cursor = decoder.cursor_; + const a = bytes[cursor + 0]; + const b = bytes[cursor + 1]; + const c = bytes[cursor + 2]; + const d = bytes[cursor + 3]; + decoder.advance(4); + return ((a << 0) | (b << 8) | (c << 16) | (d << 24)) >>> 0; + } -/** - * Reads a raw unsigned 16-bit integer from the binary stream. - * - * @return {number} The unsigned 16-bit integer read from the binary stream. - * @export - */ -jspb.BinaryDecoder.prototype.readUint16 = function() { - var a = this.bytes_[this.cursor_ + 0]; - var b = this.bytes_[this.cursor_ + 1]; - this.cursor_ += 2; - this.checkCursor(); - return (a << 0) | (b << 8); -}; + /** + * Reads a raw unsigned 64-bit integer from the binary stream. Note that since + * Javascript represents all numbers as double-precision floats, there will be + * precision lost if the absolute value of the integer is larger than 2^53. + * + * @param {!BinaryDecoder} decoder + * @return {number} The unsigned 64-bit integer read from the binary stream. + * Precision will be lost if the integer exceeds 2^53. + * @export + */ + static readUint64(decoder) { + const bitsLow = BinaryDecoder.readUint32(decoder); + const bitsHigh = BinaryDecoder.readUint32(decoder); + return utils.joinUint64(bitsLow, bitsHigh); + } + /** + * Reads a raw unsigned 64-bit integer from the binary stream. + * + * @param {!BinaryDecoder} decoder + * @return {string} The unsigned 64-bit integer read from the binary stream. + * @export + */ + static readUint64String(decoder) { + const bitsLow = BinaryDecoder.readUint32(decoder); + const bitsHigh = BinaryDecoder.readUint32(decoder); + return utils.joinUnsignedDecimalString(bitsLow, bitsHigh); + } -/** - * Reads a raw unsigned 32-bit integer from the binary stream. - * - * @return {number} The unsigned 32-bit integer read from the binary stream. - * @export - */ -jspb.BinaryDecoder.prototype.readUint32 = function() { - var a = this.bytes_[this.cursor_ + 0]; - var b = this.bytes_[this.cursor_ + 1]; - var c = this.bytes_[this.cursor_ + 2]; - var d = this.bytes_[this.cursor_ + 3]; - this.cursor_ += 4; - this.checkCursor(); - return ((a << 0) | (b << 8) | (c << 16) | (d << 24)) >>> 0; -}; + /** + * Reads a raw signed 8-bit integer from the binary stream. + * + * @param {!BinaryDecoder} decoder + * @return {number} The signed 8-bit integer read from the binary stream. + * @export + */ + static readInt8(decoder) { + const a = decoder.bytes_[decoder.cursor_ + 0]; + decoder.advance(1); + return (a << 24) >> 24; + } -/** - * Reads a raw unsigned 64-bit integer from the binary stream. Note that since - * Javascript represents all numbers as double-precision floats, there will be - * precision lost if the absolute value of the integer is larger than 2^53. - * - * @return {number} The unsigned 64-bit integer read from the binary stream. - * Precision will be lost if the integer exceeds 2^53. - * @export - */ -jspb.BinaryDecoder.prototype.readUint64 = function() { - var bitsLow = this.readUint32(); - var bitsHigh = this.readUint32(); - return jspb.utils.joinUint64(bitsLow, bitsHigh); -}; + /** + * Reads a raw signed 16-bit integer from the binary stream. + * + * @param {!BinaryDecoder} decoder + * @return {number} The signed 16-bit integer read from the binary stream. + * @export + */ + static readInt16(decoder) { + const a = decoder.bytes_[decoder.cursor_ + 0]; + const b = decoder.bytes_[decoder.cursor_ + 1]; + decoder.advance(2); + return (((a << 0) | (b << 8)) << 16) >> 16; + } -/** - * Reads a raw unsigned 64-bit integer from the binary stream. Note that since - * Javascript represents all numbers as double-precision floats, there will be - * precision lost if the absolute value of the integer is larger than 2^53. - * - * @return {string} The unsigned 64-bit integer read from the binary stream. - * @export - */ -jspb.BinaryDecoder.prototype.readUint64String = function() { - var bitsLow = this.readUint32(); - var bitsHigh = this.readUint32(); - return jspb.utils.joinUnsignedDecimalString(bitsLow, bitsHigh); -}; + /** + * Reads a raw signed 32-bit integer from the binary stream. + * + * @param {!BinaryDecoder} decoder + * @return {number} The signed 32-bit integer read from the binary stream. + * @export + */ + static readInt32(decoder) { + const bytes = decoder.bytes_; + const cursor = decoder.cursor_; + const a = bytes[cursor + 0]; + const b = bytes[cursor + 1]; + const c = bytes[cursor + 2]; + const d = bytes[cursor + 3]; + decoder.advance(4); + return (a << 0) | (b << 8) | (c << 16) | (d << 24); + } + /** + * Reads a raw signed 64-bit integer from the binary stream. Note that since + * Javascript represents all numbers as double-precision floats, there will be + * precision lost if the absolute value of the integer is larger than 2^53. + * + * @param {!BinaryDecoder} decoder + * @return {number} The signed 64-bit integer read from the binary stream. + * Precision will be lost if the integer exceeds 2^53. + * @export + */ + static readInt64(decoder) { + const bitsLow = BinaryDecoder.readUint32(decoder); + const bitsHigh = BinaryDecoder.readUint32(decoder); + return utils.joinInt64(bitsLow, bitsHigh); + } -/** - * Reads a raw signed 8-bit integer from the binary stream. - * - * @return {number} The signed 8-bit integer read from the binary stream. - * @export - */ -jspb.BinaryDecoder.prototype.readInt8 = function() { - var a = this.bytes_[this.cursor_ + 0]; - this.cursor_ += 1; - this.checkCursor(); - return (a << 24) >> 24; -}; + /** + * Reads a raw signed 64-bit integer from the binary stream and returns it as + * a string. + * + * @param {!BinaryDecoder} decoder + * @return {string} The signed 64-bit integer read from the binary stream. + * Precision will be lost if the integer exceeds 2^53. + * @export + */ + static readInt64String(decoder) { + const bitsLow = BinaryDecoder.readUint32(decoder); + const bitsHigh = BinaryDecoder.readUint32(decoder); + return utils.joinSignedDecimalString(bitsLow, bitsHigh); + } + /** + * Reads a 32-bit floating-point number from the binary stream, using the + * temporary buffer to realign the data. + * + * @param {!BinaryDecoder} decoder + * @return {number} The float read from the binary stream. + * @export + */ + static readFloat(decoder) { + const bitsLow = BinaryDecoder.readUint32(decoder); + const bitsHigh = 0; + return utils.joinFloat32(bitsLow, bitsHigh); + } -/** - * Reads a raw signed 16-bit integer from the binary stream. - * - * @return {number} The signed 16-bit integer read from the binary stream. - * @export - */ -jspb.BinaryDecoder.prototype.readInt16 = function() { - var a = this.bytes_[this.cursor_ + 0]; - var b = this.bytes_[this.cursor_ + 1]; - this.cursor_ += 2; - this.checkCursor(); - return (((a << 0) | (b << 8)) << 16) >> 16; -}; + /** + * Reads a 64-bit floating-point number from the binary stream, using the + * temporary buffer to realign the data. + * + * @param {!BinaryDecoder} decoder + * @return {number} The double read from the binary stream. + * @export + */ + static readDouble(decoder) { + if (ASSUME_DATAVIEW_IS_FAST) { + const result = decoder.getDataView().getFloat64( + decoder.cursor_, true /* little endian */); + decoder.advance(8); + return result; + } else { + const bitsLow = BinaryDecoder.readUint32(decoder); + const bitsHigh = BinaryDecoder.readUint32(decoder); + return utils.joinFloat64(bitsLow, bitsHigh); + } + } + /** + * Reads an array of 64-bit floating-point numbers from the binary stream. + * + * @param {number} len The number of doubles to read. + * @param {!Array} dst Where to append the result to. + * @export + */ + readDoubleArrayInto(len, dst) { + const cursor = this.cursor_; + const byteLength = 8 * len; + if (cursor + byteLength > this.end_) { + throw errors.readTooFarError(byteLength, this.end_ - cursor); + } -/** - * Reads a raw signed 32-bit integer from the binary stream. - * - * @return {number} The signed 32-bit integer read from the binary stream. - * @export - */ -jspb.BinaryDecoder.prototype.readInt32 = function() { - var a = this.bytes_[this.cursor_ + 0]; - var b = this.bytes_[this.cursor_ + 1]; - var c = this.bytes_[this.cursor_ + 2]; - var d = this.bytes_[this.cursor_ + 3]; - this.cursor_ += 4; - this.checkCursor(); - return (a << 0) | (b << 8) | (c << 16) | (d << 24); -}; + const bytes = this.bytes_; + const bufferStart = cursor + bytes.byteOffset; + + if (ASSUME_DATAVIEW_IS_FAST) { + // We create a subview that we'll traverse in its entirety, so that our + // "end of loop" condition matches the bounds check that the JIT will + // insert, in the hopes that the JIT only needs to run one of those checks + // instead of both. This seems to help benchmarks. + this.cursor_ += byteLength; + const subView = new DataView(bytes.buffer, bufferStart, byteLength); + let i = 0; + while (true) { + let next_i = i + 8; + if (next_i > subView.byteLength) { + break; + } + dst.push(subView.getFloat64(i, /*littleEndian=*/ true)); + i = next_i; + } + } else if (OPTIMIZE_LITTLE_ENDIAN_MACHINES && isLittleEndian()) { + // We can use Float64Array to directly interpret the bytes as float64 + // values, since this is a little endian machine. However, Float64Array + // will reject non-multiple-of-8 byte offsets, so we create a copy of the + // underlying ArrayBuffer. + this.cursor_ += byteLength; + const doubleArray = new Float64Array( + bytes.buffer.slice(bufferStart, bufferStart + byteLength)); + for (let i = 0; i < doubleArray.length; i++) { + dst.push(doubleArray[i]); + } + } else { + for (let i = 0; i < len; i++) { + dst.push(BinaryDecoder.readDouble( + this, + )); + } + } + } + /** + * Reads a boolean value from the binary stream. + * @param {!BinaryDecoder} decoder + * @return {boolean} The boolean read from the binary stream. + * @export + */ + static readBool(decoder) { + let varintBits = 0; + let cursor = decoder.cursor_; + const invalidCursor = cursor + MAX_VARINT_SIZE; + const bytes = decoder.bytes_; + while (cursor < invalidCursor) { + // scan for the first byte where the uppermost bit is 0 which signals the + // end of the varint + const byte = bytes[cursor++]; + varintBits |= byte; + if ((byte & 0x80) === 0) { + decoder.setCursorAndCheck(cursor); + // Varints store their 'values' in the lower 7 bits, so if we + // accumulated any non-zero bits then the varint is non-zero and the + // bool is true. + return !!(varintBits & 0x7f); + } + } + throw errors.invalidVarintError(); + } -/** - * Reads a raw signed 64-bit integer from the binary stream. Note that since - * Javascript represents all numbers as double-precision floats, there will be - * precision lost if the absolute value of the integer is larger than 2^53. - * - * @return {number} The signed 64-bit integer read from the binary stream. - * Precision will be lost if the integer exceeds 2^53. - * @export - */ -jspb.BinaryDecoder.prototype.readInt64 = function() { - var bitsLow = this.readUint32(); - var bitsHigh = this.readUint32(); - return jspb.utils.joinInt64(bitsLow, bitsHigh); -}; + /** + * Reads an enum value from the binary stream, which are always encoded as + * signed varints. + * @param {!BinaryDecoder} decoder + * @return {number} The enum value read from the binary stream. + * @export + */ + static readEnum(decoder) { + return BinaryDecoder.readSignedVarint32(decoder); + } + /** + * @return {number} original cursor. + * @private + */ + checkReadLengthAndAdvance(/** number */ length) { + if (length < 0) { + throw errors.negativeByteLengthError(length); + } + const cursor = this.cursor_; + const newCursor = cursor + length; + if (newCursor > this.end_) { + throw errors.readTooFarError(length, this.end_ - cursor); + } + this.cursor_ = newCursor; + return cursor; + } -/** - * Reads a raw signed 64-bit integer from the binary stream and returns it as a - * string. - * - * @return {string} The signed 64-bit integer read from the binary stream. - * Precision will be lost if the integer exceeds 2^53. - * @export - */ -jspb.BinaryDecoder.prototype.readInt64String = function() { - var bitsLow = this.readUint32(); - var bitsHigh = this.readUint32(); - return jspb.utils.joinSignedDecimalString(bitsLow, bitsHigh); -}; + /** + * Reads and parses a UTF-8 encoded unicode string from the stream. + * The code is inspired by maps.vectortown.parse.StreamedDataViewReader. + * Supports codepoints from U+0000 up to U+10FFFF. + * (http://en.wikipedia.org/wiki/UTF-8). + * @param {number} length The length of the string to read. + * @param {boolean} parsingErrorsAreFatal Whether to throw when invalid utf8 + * is found. + * @return {string} The decoded string. + * @export + */ + readString(length, parsingErrorsAreFatal) { + const cursor = this.checkReadLengthAndAdvance(length); + const result = decodeUtf8( + asserts.assert(this.bytes_), cursor, length, parsingErrorsAreFatal); + return result; + } -/** - * Reads a 32-bit floating-point number from the binary stream, using the - * temporary buffer to realign the data. - * - * @return {number} The float read from the binary stream. - * @export - */ -jspb.BinaryDecoder.prototype.readFloat = function() { - var bitsLow = this.readUint32(); - var bitsHigh = 0; - return jspb.utils.joinFloat32(bitsLow, bitsHigh); -}; + /** + * 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. + * @export + */ + readBytes(length) { + const cursor = this.checkReadLengthAndAdvance(length); + // Take care not to return mutable references to immutable data. + const result = this.aliasBytesFields && !this.bytesAreImmutable_ ? + this.bytes_.subarray(cursor, cursor + length) : + utils.sliceUint8Array( + asserts.assert(this.bytes_), cursor, cursor + length); + return result; + } + /** + * Reads a block of raw bytes from the binary stream as a ByteString + * + * @param {number} length The number of bytes to read. + * @return {!ByteString} The decoded block of bytes. + * @export + */ + readByteString(length) { + if (length == 0) { + // Special case because slice is relatively slow, even on empty slices. + return ByteString.empty(); + } + const cursor = this.checkReadLengthAndAdvance(length); + // We can return a view if the source is also immutable + const result = this.aliasBytesFields && this.bytesAreImmutable_ ? + this.bytes_.subarray(cursor, cursor + length) : + utils.sliceUint8Array( + asserts.assert(this.bytes_), cursor, cursor + length); + // no need to checkCursor, we already checked above + // This unsafe call is actually safe because we either have sliced the array + // above, or we know the source of the 'subarray' view is immutable and so + // we can reference it from an immutable ByteString + return unsafeByteStringFromUint8Array(result); + } -/** - * Reads a 64-bit floating-point number from the binary stream, using the - * temporary buffer to realign the data. - * - * @return {number} The double read from the binary stream. - * @export - */ -jspb.BinaryDecoder.prototype.readDouble = function() { - var bitsLow = this.readUint32(); - var bitsHigh = this.readUint32(); - return jspb.utils.joinFloat64(bitsLow, bitsHigh); -}; + /** + * @return {!DataView} + * @private + */ + getDataView() { + let dataView = this.dataView_; + if (!dataView) { + const bytes = this.bytes_; + dataView = this.dataView_ = + new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); + } + return dataView; + } + /** + * Visible for testing. + * @export + * @package + */ + static resetInstanceCache() { + BinaryDecoder.instanceCache_ = []; + } -/** - * Reads a boolean value from the binary stream. - * @return {boolean} The boolean read from the binary stream. - * @export - */ -jspb.BinaryDecoder.prototype.readBool = function() { - const b = !!this.bytes_[this.cursor_++]; - this.checkCursor(); - return b; -}; + /** + * Visible for testing. + * @return {!Array} + * @export + * @package + */ + static getInstanceCache() { + return BinaryDecoder.instanceCache_; + } +} /** - * Reads an enum value from the binary stream, which are always encoded as - * signed varints. - * @return {number} The enum value read from the binary stream. - * @export + * Global pool of BinaryDecoder instances. + * @private {!Array} */ -jspb.BinaryDecoder.prototype.readEnum = function() { - return this.readSignedVarint32(); -}; - +BinaryDecoder.instanceCache_ = []; /** - * Reads and parses a UTF-8 encoded unicode string from the stream. - * The code is inspired by maps.vectortown.parse.StreamedDataViewReader. - * Supports codepoints from U+0000 up to U+10FFFF. - * (http://en.wikipedia.org/wiki/UTF-8). - * @param {number} length The length of the string to read. - * @param {boolean} requireUtf8 Whether to throw when invalid utf8 is found. - * @return {string} The decoded string. - * @export + * Is this machine little endian? + * @return {boolean} */ +function isLittleEndian() { + if (isLittleEndianCache === undefined) { + // Do a trial memory load. + isLittleEndianCache = + new Uint16Array(new Uint8Array([1, 2]).buffer)[0] == (1 + 256 * 2); + } + return asserts.assertBoolean(isLittleEndianCache); +} -jspb.BinaryDecoder.prototype.readString = function (length, requireUtf8) { - const cursor = this.cursor_; - this.cursor_ += length; - this.checkCursor(); - const result = - jspb.binary.utf8.decodeUtf8(jspb.asserts.assert(this.bytes_), cursor, length, requireUtf8); - return result; -}; +/** Global cache of the isLittleEndian check. */ +let isLittleEndianCache = undefined; /** - * 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 an empty block if the - * length was invalid. - * @export + * @define {boolean} + * Whether DataView is known to be present and has a fast implementation. + * Chrome's implementation was slow until late 2018: + * https://v8.dev/blog/dataview. Unsure of the performance on other browsers. */ -jspb.BinaryDecoder.prototype.readBytes = function(length) { - if (length < 0 || this.cursor_ + length > this.bytes_.length) { - this.error_ = true; - jspb.asserts.fail('Invalid byte length!'); - return new Uint8Array(0); - } - - var result = this.bytes_.subarray(this.cursor_, this.cursor_ + length); - - this.cursor_ += length; - this.checkCursor(); - return result; -}; - +const ASSUME_DATAVIEW_IS_FAST = goog.define( + 'jspb.BinaryDecoder.ASSUME_DATAVIEW_IS_FAST', goog.FEATURESET_YEAR >= 2019); /** - * Reads a 64-bit varint from the stream and returns it as an 8-character - * Unicode string for use as a hash table key. + * @define {boolean} If true, on little endian machines we'll enable the fast + * path for float64 decoding. * - * @return {string} The hash value. - * @export + * Only exists for testing. */ -jspb.BinaryDecoder.prototype.readVarintHash64 = function() { - return this.readSplitVarint64(jspb.utils.joinHash64); -}; +const OPTIMIZE_LITTLE_ENDIAN_MACHINES = + goog.define('jspb.BinaryDecoder.OPTIMIZE_LITTLE_ENDIAN_MACHINES', true); -/** - * Reads a 64-bit fixed-width value from the stream and returns it as an - * 8-character Unicode string for use as a hash table key. - * - * @return {string} The hash value. - * @export - */ -jspb.BinaryDecoder.prototype.readFixedHash64 = function() { - var bytes = this.bytes_; - var cursor = this.cursor_; - - var a = bytes[cursor + 0]; - var b = bytes[cursor + 1]; - var c = bytes[cursor + 2]; - var d = bytes[cursor + 3]; - var e = bytes[cursor + 4]; - var f = bytes[cursor + 5]; - var g = bytes[cursor + 6]; - var h = bytes[cursor + 7]; - - this.cursor_ += 8; - - return String.fromCharCode(a, b, c, d, e, f, g, h); +exports = { + BinaryDecoder, }; diff --git a/binary/decoder_alias.js b/binary/decoder_alias.js new file mode 100755 index 0000000..42367e9 --- /dev/null +++ b/binary/decoder_alias.js @@ -0,0 +1,9 @@ +/** + * @fileoverview Legacy alias for the old namespace used by encoder.js + */ +goog.module('jspb.BinaryDecoder'); +goog.module.declareLegacyNamespace(); + +const {BinaryDecoder} = goog.require('jspb.binary.decoder'); + +exports = BinaryDecoder; diff --git a/binary/decoder_test.js b/binary/decoder_test.js old mode 100644 new mode 100755 index 7a71c0d..626118f --- a/binary/decoder_test.js +++ b/binary/decoder_test.js @@ -42,24 +42,41 @@ */ goog.require('jspb.BinaryConstants'); -goog.require('jspb.BinaryDecoder'); -goog.require('jspb.BinaryEncoder'); - goog.require('jspb.utils'); +goog.require('jspb.binary.decoder'); +goog.require('jspb.binary.encoder'); +goog.require('jspb.binary.utf8'); +goog.require('jspb.bytestring'); + +const BinaryConstants = goog.module.get('jspb.BinaryConstants'); +const utils = goog.module.get('jspb.utils'); +const BinaryDecoder = goog.module.get('jspb.binary.decoder').BinaryDecoder; +const BinaryEncoder = goog.module.get('jspb.binary.encoder').BinaryEncoder; +const ByteString = goog.module.get('jspb.bytestring').ByteString; +const encodeUtf8 = goog.module.get('jspb.binary.utf8').encodeUtf8; +/** + * + * @param {number|string|bigint} x + * @returns number + */ +function asNumberOrString(x) { + const num = Number(x); + return Number.isSafeInteger(num) ? num : (/** @type{number} */(String(x))); +} /** * Tests encoding and decoding of unsigned types. - * @param {Function} readValue - * @param {Function} writeValue + * @param {!Function} readValue + * @param {!Function} writeValue * @param {number} epsilon * @param {number} upperLimit - * @param {Function} filter + * @param {!Function} filter * @suppress {missingProperties|visibility} */ -function doTestUnsignedValue( - readValue, writeValue, epsilon, upperLimit, filter) { - const encoder = new jspb.BinaryEncoder(); +function doTestUnsignedValue(readValue, + writeValue, epsilon, upperLimit, filter) { + const encoder = new BinaryEncoder(); // Encode zero and limits. writeValue.call(encoder, filter(0)); @@ -71,16 +88,16 @@ function doTestUnsignedValue( writeValue.call(encoder, filter(cursor)); } - const decoder = jspb.BinaryDecoder.alloc(encoder.end()); + const decoder = BinaryDecoder.alloc(encoder.end()); // Check zero and limits. - expect(readValue.call(decoder)).toEqual(filter(0)); - expect(readValue.call(decoder)).toEqual(filter(epsilon)); - expect(readValue.call(decoder)).toEqual(filter(upperLimit)); + expect(readValue(decoder)).toEqual(filter(0)); + expect(readValue(decoder)).toEqual(filter(epsilon)); + expect(readValue(decoder)).toEqual(filter(upperLimit)); // Check positive values. for (let cursor = epsilon; cursor < upperLimit; cursor *= 1.1) { - if (filter(cursor) != readValue.call(decoder)) throw 'fail!'; + if (filter(cursor) != readValue(decoder)) throw 'fail!'; } // Encoding values outside the valid range should assert. @@ -92,20 +109,19 @@ function doTestUnsignedValue( }).toThrow(); } - /** * Tests encoding and decoding of signed types. - * @param {Function} readValue - * @param {Function} writeValue + * @param {!Function} readValue + * @param {!Function} writeValue * @param {number} epsilon * @param {number} lowerLimit * @param {number} upperLimit - * @param {Function} filter + * @param {!Function} filter * @suppress {missingProperties} */ -function doTestSignedValue( - readValue, writeValue, epsilon, lowerLimit, upperLimit, filter) { - const encoder = new jspb.BinaryEncoder(); +function doTestSignedValue(readValue, + writeValue, epsilon, lowerLimit, upperLimit, filter) { + const encoder = new BinaryEncoder(); // Encode zero and limits. writeValue.call(encoder, filter(lowerLimit)); @@ -118,7 +134,7 @@ function doTestSignedValue( // Encode negative values. for (let cursor = lowerLimit; cursor < -epsilon; cursor /= 1.1) { - let val = filter(cursor); + const val = filter(cursor); writeValue.call(encoder, val); inputValues.push(val); } @@ -130,18 +146,25 @@ function doTestSignedValue( inputValues.push(val); } - const decoder = jspb.BinaryDecoder.alloc(encoder.end()); + const decoder = BinaryDecoder.alloc(encoder.end()); // Check zero and limits. - expect(readValue.call(decoder)).toEqual(filter(lowerLimit)); - expect(readValue.call(decoder)).toEqual(filter(-epsilon)); - expect(readValue.call(decoder)).toEqual(filter(0)); - expect(readValue.call(decoder)).toEqual(filter(epsilon)); - expect(readValue.call(decoder)).toEqual(filter(upperLimit)); + expect(readValue(decoder)).toEqual(filter(lowerLimit)); + expect(readValue(decoder)).toEqual(filter(-epsilon)); + expect(readValue(decoder)).toEqual(filter(0)); + expect(readValue(decoder)).toEqual(filter(epsilon)); + expect(readValue(decoder)).toEqual(filter(upperLimit)); // Verify decoded values. + const nestedData = + new Uint8Array(decoder.getBuffer().buffer, decoder.getCursor()); + for (let i = 0; i < inputValues.length; i++) { + expect(readValue(decoder)).toEqual(inputValues[i]); + } + // Verify we can read from a nested Uint8Array. + const nestedDecoder = BinaryDecoder.alloc(nestedData); for (let i = 0; i < inputValues.length; i++) { - expect(readValue.call(decoder)).toEqual(inputValues[i]); + expect(readValue(nestedDecoder)).toEqual(inputValues[i]); } // Encoding values outside the valid range should assert. @@ -155,349 +178,411 @@ function doTestSignedValue( } } +/** + * @param {function(!BinaryDecoder):number} readValue + * @param {function(!BinaryEncoder, number)} writeValue + */ +function doTestSigned64BitIntValue( + readValue, + writeValue +) { + const encoder = new BinaryEncoder(); + + const lowerLimit = -Math.pow(2, 63); + const upperLimit = Math.pow(2, 63) - 1024; + + const inputValues = []; + function addValue(/** number */v, /**string|undefined*/withPrecision) { + writeValue.call(encoder, v); + inputValues.push(withPrecision ? asNumberOrString(withPrecision) : v); + } + // Encode zero and limits. + addValue(lowerLimit, '-9223372036854775808'); + addValue(-1); + addValue(0); + addValue(1); + addValue(upperLimit, '9223372036854774784'); + + // Encode negative values. + for ( + let cursor = BigInt('-9223372036854775808'); + cursor < BigInt(-1); + cursor /= BigInt(2) + ) { + addValue(Number(cursor), String(cursor)); + } + + // Encode positive values. + for ( + let cursor = BigInt(1); + cursor < BigInt('9223372036854774784'); + cursor *= BigInt(2) + ) { + addValue(Number(cursor), String(cursor)); + } + + const encodedData = encoder.end(); + const decoder = BinaryDecoder.alloc(encodedData); + const u8 = new Uint8Array(encodedData.length + 5); + u8.set(encodedData, 5); + // Verify we can read from a Uint8Array that has a byteoffset + const offsetDecoder = BinaryDecoder.alloc(new Uint8Array(u8.buffer, 5)); + + // Verify decoded values. + for (let i = 0; i < inputValues.length; i++) { + expect(readValue(decoder)).toBe(inputValues[i]); + expect(readValue(offsetDecoder)).toBe(inputValues[i]); + } + // Encoding values outside the valid range should assert. + const pastLowerLimit = lowerLimit * 1.1; + const pastUpperLimit = upperLimit * 1.1; + expect(() => { writeValue.call(encoder, pastLowerLimit); }).toThrow(); + expect(() => { writeValue.call(encoder, pastUpperLimit); }).toThrow(); +} + +/** + * @param {function(!BinaryDecoder):number} readValue + * @param {function(!BinaryEncoder, number)} writeValue + */ +function doTestUnsigned64BitIntValue( + readValue, + writeValue +) { + const encoder = new BinaryEncoder(); + const upperLimit = Math.pow(2, 64) - 2048; + + // Encode zero and limits. + writeValue.call(encoder, 0); + writeValue.call(encoder, 1); + writeValue.call(encoder, upperLimit); + // Encode positive values. + for ( + let cursor = BigInt(1); + cursor < BigInt('18446744073709549568'); + cursor *= BigInt(2) + ) { + writeValue.call(encoder, asNumberOrString(cursor.toString())); + } + + const decoder = BinaryDecoder.alloc(encoder.end()); + + // Check zero and limits. + expect(readValue(decoder)).toEqual(0); + expect(readValue(decoder)).toEqual(1); + expect(readValue(decoder)).toEqual( + /** @type{number}*/('18446744073709549568') + ); + + // Check positive values. + for ( + let cursor = BigInt(1); + cursor < BigInt('18446744073709549568'); + cursor *= BigInt(2) + ) { + expect(readValue(decoder)).toEqual(asNumberOrString(cursor.toString())); + } + + // Encoding values outside the valid range should assert. + expect(() => { + writeValue.call(encoder, -1); + }).toThrow(); + expect(() => { + writeValue.call( + encoder, + Number( + BigInt('18446744073709549568') * BigInt(2), + ), + ); + }).toThrow(); +} + describe('binaryDecoderTest', () => { - /** - * Tests the decoder instance cache. - */ - it('testInstanceCache', /** @suppress {visibility} */ () => { + /** Tests the decoder instance cache. */ + it('testInstanceCache', () => { // Empty the instance caches. - jspb.BinaryDecoder.instanceCache_ = []; + BinaryDecoder.resetInstanceCache(); // Allocating and then freeing a decoder should put it in the instance // cache. - jspb.BinaryDecoder.alloc().free(); + BinaryDecoder.alloc().free(); - expect(jspb.BinaryDecoder.getInstanceCacheLength()).toEqual(1); + expect(BinaryDecoder.getInstanceCache().length).toEqual(1); // Allocating and then freeing three decoders should leave us with three in // the cache. - const decoder1 = jspb.BinaryDecoder.alloc(); - const decoder2 = jspb.BinaryDecoder.alloc(); - const decoder3 = jspb.BinaryDecoder.alloc(); + const decoder1 = BinaryDecoder.alloc(); + const decoder2 = BinaryDecoder.alloc(); + const decoder3 = BinaryDecoder.alloc(); decoder1.free(); decoder2.free(); decoder3.free(); - expect(jspb.BinaryDecoder.getInstanceCacheLength()).toEqual(3); + expect(BinaryDecoder.getInstanceCache().length).toEqual(3); }); - describe('varint64', () => { - let /** !jspb.BinaryEncoder */ encoder; - let /** !jspb.BinaryDecoder */ decoder; - - const hashA = - String.fromCharCode(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); - const hashB = - String.fromCharCode(0x12, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); - const hashC = - String.fromCharCode(0x12, 0x34, 0x56, 0x78, 0x87, 0x65, 0x43, 0x21); - const hashD = - String.fromCharCode(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF); - beforeEach(() => { - encoder = new jspb.BinaryEncoder(); - - encoder.writeVarintHash64(hashA); - encoder.writeVarintHash64(hashB); - encoder.writeVarintHash64(hashC); - encoder.writeVarintHash64(hashD); + let /** !BinaryEncoder */ encoder; + let /** !BinaryDecoder */ decoder; - encoder.writeFixedHash64(hashA); - encoder.writeFixedHash64(hashB); - encoder.writeFixedHash64(hashC); - encoder.writeFixedHash64(hashD); + const a = { lo: 0x00000000, hi: 0x00000000 }; + const b = { lo: 0x00003412, hi: 0x00000000 }; + const c = { lo: 0x78563412, hi: 0x21436587 }; + const d = { lo: 0xFFFFFFFF, hi: 0xFFFFFFFF }; + beforeEach(() => { + encoder = new BinaryEncoder(); - decoder = jspb.BinaryDecoder.alloc(encoder.end()); - }); + encoder.writeSplitVarint64(a.lo, a.hi); + encoder.writeSplitVarint64(b.lo, b.hi); + encoder.writeSplitVarint64(c.lo, c.hi); + encoder.writeSplitVarint64(d.lo, d.hi); - it('reads 64-bit integers as hash strings', () => { - expect(hashA).toEqual(decoder.readVarintHash64()); - expect(hashB).toEqual(decoder.readVarintHash64()); - expect(hashC).toEqual(decoder.readVarintHash64()); - expect(hashD).toEqual(decoder.readVarintHash64()); + encoder.writeSplitFixed64(a.lo, a.hi); + encoder.writeSplitFixed64(b.lo, b.hi); + encoder.writeSplitFixed64(c.lo, c.hi); + encoder.writeSplitFixed64(d.lo, d.hi); - expect(hashA).toEqual(decoder.readFixedHash64()); - expect(hashB).toEqual(decoder.readFixedHash64()); - expect(hashC).toEqual(decoder.readFixedHash64()); - expect(hashD).toEqual(decoder.readFixedHash64()); + decoder = BinaryDecoder.alloc(encoder.end()); }); it('reads split 64 bit integers', () => { function hexJoin(bitsLow, bitsHigh) { - return `0x${(bitsHigh >>> 0).toString(16)}:0x${ - (bitsLow >>> 0).toString(16)}`; + return `0x${(bitsHigh >>> 0).toString(16)}:0x${(bitsLow >>> 0).toString(16)}`; } - function hexJoinHash(hash64) { - jspb.utils.splitHash64(hash64, true); - - return hexJoin(jspb.utils.getSplit64Low(), jspb.utils.getSplit64High()); + function hexJoinPair(p) { + return hexJoin(p.lo, p.hi); } - expect(decoder.readSplitVarint64(hexJoin)).toEqual(hexJoinHash(hashA)); - expect(decoder.readSplitVarint64(hexJoin)).toEqual(hexJoinHash(hashB)); - expect(decoder.readSplitVarint64(hexJoin)).toEqual(hexJoinHash(hashC)); - expect(decoder.readSplitVarint64(hexJoin)).toEqual(hexJoinHash(hashD)); + expect(BinaryDecoder.readSplitVarint64(decoder, hexJoin)).toEqual(hexJoinPair(a)); + expect(BinaryDecoder.readSplitVarint64(decoder, hexJoin)).toEqual(hexJoinPair(b)); + expect(BinaryDecoder.readSplitVarint64(decoder, hexJoin)).toEqual(hexJoinPair(c)); + expect(BinaryDecoder.readSplitVarint64(decoder, hexJoin)).toEqual(hexJoinPair(d)); - expect(decoder.readSplitFixed64(hexJoin)).toEqual(hexJoinHash(hashA)); - expect(decoder.readSplitFixed64(hexJoin)).toEqual(hexJoinHash(hashB)); - expect(decoder.readSplitFixed64(hexJoin)).toEqual(hexJoinHash(hashC)); - expect(decoder.readSplitFixed64(hexJoin)).toEqual(hexJoinHash(hashD)); + expect(BinaryDecoder.readSplitFixed64(decoder, hexJoin)).toEqual(hexJoinPair(a)); + expect(BinaryDecoder.readSplitFixed64(decoder, hexJoin)).toEqual(hexJoinPair(b)); + expect(BinaryDecoder.readSplitFixed64(decoder, hexJoin)).toEqual(hexJoinPair(c)); + expect(BinaryDecoder.readSplitFixed64(decoder, hexJoin)).toEqual(hexJoinPair(d)); + }); + it('split 64 out of range', () => { + decoder = BinaryDecoder.alloc(new Uint8Array([0, 1, 2])); + expect(() => BinaryDecoder.readSplitFixed64(decoder, (hi, low) => { + throw new Error(); + })).toThrowError('Tried to read past the end of the data 8 > 3'); }); }); describe('sint64', () => { - let /** !jspb.BinaryDecoder */ decoder; - - const hashA = - String.fromCharCode(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); - const hashB = - String.fromCharCode(0x12, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); - const hashC = - String.fromCharCode(0x12, 0x34, 0x56, 0x78, 0x87, 0x65, 0x43, 0x21); - const hashD = - String.fromCharCode(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF); + let /** !BinaryDecoder */ decoder; + + const a = { lo: 0x00000000, hi: 0x00000000 }; + const b = { lo: 0x00003412, hi: 0x00000000 }; + const c = { lo: 0x78563412, hi: 0x21436587 }; + const d = { lo: 0xFFFFFFFF, hi: 0xFFFFFFFF }; beforeEach(() => { - const encoder = new jspb.BinaryEncoder(); + const encoder = new BinaryEncoder(); - encoder.writeZigzagVarintHash64(hashA); - encoder.writeZigzagVarintHash64(hashB); - encoder.writeZigzagVarintHash64(hashC); - encoder.writeZigzagVarintHash64(hashD); + encoder.writeSplitZigzagVarint64(a.lo, a.hi); + encoder.writeSplitZigzagVarint64(b.lo, b.hi); + encoder.writeSplitZigzagVarint64(c.lo, c.hi); + encoder.writeSplitZigzagVarint64(d.lo, d.hi); - decoder = jspb.BinaryDecoder.alloc(encoder.end()); + decoder = BinaryDecoder.alloc(encoder.end()); }); it('reads 64-bit integers as decimal strings', () => { - 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', () => { - expect(decoder.readZigzagVarintHash64()).toEqual(hashA); - expect(decoder.readZigzagVarintHash64()).toEqual(hashB); - expect(decoder.readZigzagVarintHash64()).toEqual(hashC); - expect(decoder.readZigzagVarintHash64()).toEqual(hashD); + expect(BinaryDecoder.readZigzagVarint64String(decoder)) + .toEqual(utils.joinSignedDecimalString(a.lo, a.hi)); + expect(BinaryDecoder.readZigzagVarint64String(decoder)) + .toEqual(utils.joinSignedDecimalString(b.lo, b.hi)); + expect(BinaryDecoder.readZigzagVarint64String(decoder)) + .toEqual(utils.joinSignedDecimalString(c.lo, c.hi)); + expect(BinaryDecoder.readZigzagVarint64String(decoder)) + .toEqual(utils.joinSignedDecimalString(d.lo, d.hi)); }); it('reads split 64 bit zigzag integers', () => { function hexJoin(bitsLow, bitsHigh) { - return `0x${(bitsHigh >>> 0).toString(16)}:0x${ - (bitsLow >>> 0).toString(16)}`; + return `0x${(bitsHigh >>> 0).toString(16)}:0x${(bitsLow >>> 0).toString(16)}`; } - function hexJoinHash(hash64) { - jspb.utils.splitHash64(hash64); - return hexJoin(jspb.utils.getSplit64Low(), jspb.utils.getSplit64High()); + function hexJoinPair(p) { + return hexJoin(p.lo, p.hi); } - 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)); + expect(BinaryDecoder.readSplitZigzagVarint64(decoder, hexJoin)).toEqual(hexJoinPair(a)); + expect(BinaryDecoder.readSplitZigzagVarint64(decoder, hexJoin)).toEqual(hexJoinPair(b)); + expect(BinaryDecoder.readSplitZigzagVarint64(decoder, hexJoin)).toEqual(hexJoinPair(c)); + expect(BinaryDecoder.readSplitZigzagVarint64(decoder, hexJoin)).toEqual(hexJoinPair(d)); }); it('does zigzag encoding properly', () => { - // Test cases directly from the protobuf dev guide. + // Test cases direcly from the protobuf dev guide. // https://engdoc.corp.google.com/eng/howto/protocolbuffers/developerguide/encoding.shtml?cl=head#types const 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'}, + { 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'}, - // None of the above catch: bitsLow < 0 && bitsHigh > 0 && bitsHigh < - // 0x1FFFFF. The following used to be broken. - {original: '72000000000', zigzag: '144000000000'}, + { original: '9223372036854775807', zigzag: '18446744073709551614' }, + { original: '-9223372036854775808', zigzag: '18446744073709551615' }, ]; - const encoder = new jspb.BinaryEncoder(); - testCases.forEach(function(c) { + const encoder = new BinaryEncoder(); + testCases.forEach((c) => { encoder.writeZigzagVarint64String(c.original); }); const buffer = encoder.end(); - const zigzagDecoder = jspb.BinaryDecoder.alloc(buffer); - const varintDecoder = jspb.BinaryDecoder.alloc(buffer); - testCases.forEach(function(c) { - expect(zigzagDecoder.readZigzagVarint64String()).toEqual(c.original); - expect(varintDecoder.readUnsignedVarint64String()).toEqual(c.zigzag); + const zigzagDecoder = BinaryDecoder.alloc(buffer); + const varintDecoder = BinaryDecoder.alloc(buffer); + testCases.forEach((c) => { + expect(BinaryDecoder.readZigzagVarint64String(zigzagDecoder)).toEqual(c.original); + expect(BinaryDecoder.readUnsignedVarint64String(varintDecoder)).toEqual(c.zigzag); }); }); }); - /** - * Tests reading and writing large strings - */ + /** Tests reading and writing large strings */ it('testLargeStrings', () => { - const encoder = new jspb.BinaryEncoder(); - const len = 150000; let long_string = ''; for (let i = 0; i < len; i++) { long_string += 'a'; } - - encoder.writeString(long_string); - - const decoder = jspb.BinaryDecoder.alloc(encoder.end()); + const decoder = BinaryDecoder.alloc(encodeUtf8(long_string)); expect(decoder.readString(len, true)).toEqual(long_string); }); - /** - * Test encoding and decoding utf-8. - */ + /** Test encoding and decoding utf-8. */ it('testUtf8', () => { - const encoder = new jspb.BinaryEncoder(); const ascii = 'ASCII should work in 3, 2, 1...'; const utf8_two_bytes = '©'; const utf8_three_bytes = '❄'; const utf8_four_bytes = '😁'; - encoder.writeString(ascii); - encoder.writeString(utf8_two_bytes); - encoder.writeString(utf8_three_bytes); - encoder.writeString(utf8_four_bytes); + expect(encodeUtf8(ascii).length).toBe(ascii.length); + expect(encodeUtf8(utf8_two_bytes).length).toBe(2); + expect(encodeUtf8(utf8_three_bytes).length).toBe(3); + expect(encodeUtf8(utf8_four_bytes).length).toBe(4); - const decoder = jspb.BinaryDecoder.alloc(encoder.end()); + const decoder = BinaryDecoder.alloc(new Uint8Array([ + ...encodeUtf8(ascii), ...encodeUtf8(utf8_two_bytes), ...encodeUtf8(utf8_three_bytes), ...encodeUtf8(utf8_four_bytes) + ])); - expect(decoder.readString(ascii.length, /* enforceUtf8= */ true)).toEqual(ascii); - expect(utf8_two_bytes).toEqual(decoder.readString(2, /* enforceUtf8= */ true)); - expect(utf8_three_bytes) - .toEqual(decoder.readString(3, /* enforceUtf8= */ true)); - expect(utf8_four_bytes).toEqual(decoder.readString(4, /* enforceUtf8= */ true)); + expect(decoder.readString(ascii.length, true)).toEqual(ascii); + expect(decoder.readString(2, true)).toEqual(utf8_two_bytes); + expect(decoder.readString(3, true)).toEqual(utf8_three_bytes); + expect(decoder.readString(4, true)).toEqual(utf8_four_bytes); }); - /** - * Verifies that passing a non-string to writeString raises an error. - */ - it('testBadString', () => { - const encoder = new jspb.BinaryEncoder(); - - expect(() => { - encoder.writeString(42) - }).toThrow(); - expect(() => { - encoder.writeString(null) - }).toThrow(); + it('test invalid utf8, repro b/191931501', () => { + // "J\x06*e\xA9`\xF8'H8\x05\xC0" an errant value from b/191931501 + const decoder = BinaryDecoder.alloc(new Uint8Array([ + 0x4A, 0x06, 0x2A, 0x65, 0xA9, 0x60, 0xF8, 0x27, 0x48, 0x38, 0x05, 0xC0 + ])); + // This text is corrupted! Not surprising since it is invalid utf8 of + // course. + expect(decoder.readString(12, false)).toEqual('J\u0006*e�`�\'H8\u0005�'); + expect(decoder.getCursor()).toEqual(12); // but we didn't read past the end + decoder.setCursor(0); + // When we set requireUtf8, we get an error. + expect(() => decoder.readString(12, true)).toThrow(); }); - /** - * Verifies that misuse of the decoder class triggers assertions. - */ - it('testDecodeErrors', () => { - // Reading a value past the end of the stream should trigger an assertion. - const decoder = jspb.BinaryDecoder.alloc([0, 1, 2]); - expect(() => { - decoder.readUint64() - }).toThrow(); - - // Overlong varints should trigger assertions. - decoder.setBlock( - [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0]); - expect(() => { - decoder.readUnsignedVarint64() - }).toThrow(); - decoder.reset(); - expect(() => { - decoder.readSignedVarint64() - }).toThrow(); - decoder.reset(); - expect(() => { - decoder.readZigzagVarint64() - }).toThrow(); - decoder.reset(); - expect(() => { - decoder.readUnsignedVarint32() - }).toThrow(); - }); + describe('bytes', () => { + let /** Uint8Array */source; + let /** Uint8Array */sourceCopy; + beforeEach(() => { + source = new Uint8Array([1, 2, 3, 255, 1, 2, 8, 4]); + sourceCopy = new Uint8Array(source); + }); - /** - * Tests encoding and decoding of unsigned integers. - */ - it('testUnsignedIntegers', () => { - doTestUnsignedValue( - jspb.BinaryDecoder.prototype.readUint8, - jspb.BinaryEncoder.prototype.writeUint8, 1, 0xFF, Math.round); + it('empty readBytes', () => { + expect(BinaryDecoder.alloc(source).readBytes(0)).toEqual( + new Uint8Array(0), + ); + }); + it('empty readByteString', () => { + expect(BinaryDecoder.alloc(source).readByteString(0)).toBe( + ByteString.empty(), + ); + }); - doTestUnsignedValue( - jspb.BinaryDecoder.prototype.readUint16, - jspb.BinaryEncoder.prototype.writeUint16, 1, 0xFFFF, Math.round); + it('mutations to the source are not reflected in the output', () => { + const decoded = BinaryDecoder.alloc(source).readBytes(source.length); - doTestUnsignedValue( - jspb.BinaryDecoder.prototype.readUint32, - jspb.BinaryEncoder.prototype.writeUint32, 1, 0xFFFFFFFF, Math.round); + expect(decoded).toEqual(sourceCopy); + // Change the source array and ensure the underlying data was copied. + source.set(new Uint8Array([7, 6, 5, 4, 3, 2, 1, 101])); + + // Assert that the decoded array hasn't changed. + expect(decoded).toEqual(sourceCopy); + }); + + it('mutations to the source are not reflected in the output ByteString', () => { + const decoded = BinaryDecoder.alloc(source).readByteString(source.length); + expect(decoded.asUint8Array()).toEqual(sourceCopy); + // Change the source array and ensure the underlying data was copied. + source.set(new Uint8Array([7, 6, 5, 4, 3, 2, 1, 101])); + + // Assert that the decoded array hasn't changed. + expect(decoded.asUint8Array()).toEqual(sourceCopy); + }); + + it('mutations to the output are not reflected in the source', () => { + const decoded = BinaryDecoder.alloc(source).readBytes(source.length); + + // Change the output array and ensure the underlying data was copied. + decoded.set(new Uint8Array([7, 6, 5, 4, 3, 2, 1, 101])); + + // Assert that the source hasn't changed. + expect(sourceCopy).toEqual(source); + }); + }); + + + /** Tests encoding and decoding of unsigned integers. */ + it('testUnsignedIntegers', () => { doTestUnsignedValue( - jspb.BinaryDecoder.prototype.readUnsignedVarint32, - jspb.BinaryEncoder.prototype.writeUnsignedVarint32, 1, 0xFFFFFFFF, - Math.round); + BinaryDecoder.readUint8, BinaryEncoder.prototype.writeUint8, + 1, 0xFF, Math.round); doTestUnsignedValue( - jspb.BinaryDecoder.prototype.readUint64, - jspb.BinaryEncoder.prototype.writeUint64, 1, Math.pow(2, 64) - 1025, - Math.round); + BinaryDecoder.readUint16, BinaryEncoder.prototype.writeUint16, + 1, 0xFFFF, Math.round); doTestUnsignedValue( - jspb.BinaryDecoder.prototype.readUnsignedVarint64, - jspb.BinaryEncoder.prototype.writeUnsignedVarint64, 1, - Math.pow(2, 64) - 1025, Math.round); - }); + BinaryDecoder.readUint32, BinaryEncoder.prototype.writeUint32, + 1, 0xFFFFFFFF, Math.round); + doTestUnsigned64BitIntValue(BinaryDecoder.readUint64, + BinaryEncoder.prototype.writeUint64); + }); - /** - * Tests encoding and decoding of signed integers. - */ + /** Tests encoding and decoding of signed integers. */ it('testSignedIntegers', () => { doTestSignedValue( - jspb.BinaryDecoder.prototype.readInt8, - jspb.BinaryEncoder.prototype.writeInt8, 1, -0x80, 0x7F, Math.round); + BinaryDecoder.readInt8, BinaryEncoder.prototype.writeInt8, 1, + -0x80, 0x7F, Math.round); doTestSignedValue( - jspb.BinaryDecoder.prototype.readInt16, - jspb.BinaryEncoder.prototype.writeInt16, 1, -0x8000, 0x7FFF, - Math.round); + BinaryDecoder.readInt16, BinaryEncoder.prototype.writeInt16, + 1, -0x8000, 0x7FFF, Math.round); doTestSignedValue( - jspb.BinaryDecoder.prototype.readInt32, - jspb.BinaryEncoder.prototype.writeInt32, 1, -0x80000000, 0x7FFFFFFF, - Math.round); + BinaryDecoder.readInt32, BinaryEncoder.prototype.writeInt32, + 1, -0x80000000, 0x7FFFFFFF, Math.round); - doTestSignedValue( - jspb.BinaryDecoder.prototype.readSignedVarint32, - jspb.BinaryEncoder.prototype.writeSignedVarint32, 1, -0x80000000, - 0x7FFFFFFF, Math.round); - - doTestSignedValue( - jspb.BinaryDecoder.prototype.readInt64, - jspb.BinaryEncoder.prototype.writeInt64, 1, -Math.pow(2, 63), - Math.pow(2, 63) - 513, Math.round); - doTestSignedValue( - jspb.BinaryDecoder.prototype.readSignedVarint64, - jspb.BinaryEncoder.prototype.writeSignedVarint64, 1, -Math.pow(2, 63), - Math.pow(2, 63) - 513, Math.round); + doTestSigned64BitIntValue( + BinaryDecoder.readInt64, BinaryEncoder.prototype.writeInt64); }); - - /** - * Tests encoding and decoding of floats. - */ + /** Tests encoding and decoding of floats. */ it('testFloats', () => { /** * @param {number} x @@ -509,18 +594,13 @@ describe('binaryDecoderTest', () => { return temp[0]; } doTestSignedValue( - jspb.BinaryDecoder.prototype.readFloat, - jspb.BinaryEncoder.prototype.writeFloat, - jspb.BinaryConstants.FLOAT32_EPS, -jspb.BinaryConstants.FLOAT32_MAX, - jspb.BinaryConstants.FLOAT32_MAX, truncate); + BinaryDecoder.readFloat, BinaryEncoder.prototype.writeFloat, + BinaryConstants.FLOAT32_EPS, -BinaryConstants.FLOAT32_MAX, + BinaryConstants.FLOAT32_MAX, truncate); doTestSignedValue( - jspb.BinaryDecoder.prototype.readDouble, - jspb.BinaryEncoder.prototype.writeDouble, - jspb.BinaryConstants.FLOAT64_EPS * 10, - -jspb.BinaryConstants.FLOAT64_MAX, jspb.BinaryConstants.FLOAT64_MAX, - function(x) { - return x; - }); + BinaryDecoder.readDouble, BinaryEncoder.prototype.writeDouble, + BinaryConstants.FLOAT64_EPS * 10, -BinaryConstants.FLOAT64_MAX, + BinaryConstants.FLOAT64_MAX, (x) => x); }); }); diff --git a/binary/encoder.js b/binary/encoder.js old mode 100644 new mode 100755 index d6e35c6..216bd14 --- a/binary/encoder.js +++ b/binary/encoder.js @@ -1,33 +1,3 @@ -// Protocol Buffers - Google's data interchange format -// Copyright 2008 Google Inc. All rights reserved. -// https://protobuf.dev/ -// -// 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. @@ -35,512 +5,376 @@ * @author aappleby@google.com (Austin Appleby) */ -goog.provide('jspb.BinaryEncoder'); - -goog.require('jspb.asserts'); -goog.require('jspb.BinaryConstants'); -goog.require('jspb.utils'); - +goog.module('jspb.binary.encoder'); +goog.module.declareLegacyNamespace(); +const BinaryConstants = goog.require('jspb.BinaryConstants'); +const asserts = goog.require('goog.asserts'); +const utils = goog.require('jspb.utils'); +// The maximum number of bytes to push onto `buffer_` at a time, limited to +// prevent stack overflow errors. +const MAX_PUSH = 8192; /** * BinaryEncoder implements encoders for all the wire types specified in - * https://protobuf.dev/programming-guides/encoding/. - * - * @constructor - * @struct - * @export - */ -jspb.BinaryEncoder = function() { - /** @private {!Array} */ - this.buffer_ = []; -}; - - -/** - * @return {number} - * @export - */ -jspb.BinaryEncoder.prototype.length = function() { - return this.buffer_.length; -}; - - -/** - * @return {!Array} - * @export - */ -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. - * @export + * https://developers.google.com/protocol-buffers/docs/encoding. */ -jspb.BinaryEncoder.prototype.writeSplitVarint64 = function(lowBits, highBits) { - jspb.asserts.assert(lowBits == Math.floor(lowBits)); - jspb.asserts.assert(highBits == Math.floor(highBits)); - jspb.asserts.assert( - (lowBits >= 0) && (lowBits < jspb.BinaryConstants.TWO_TO_32)); - jspb.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; +class BinaryEncoder { + constructor() { + /** @private {!Array} */ + this.buffer_ = []; } - this.buffer_.push(lowBits); -}; - - -/** - * Encodes a 64-bit integer in 32:32 split representation into its wire-format - * fixed 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. - * @export - */ -jspb.BinaryEncoder.prototype.writeSplitFixed64 = function(lowBits, highBits) { - jspb.asserts.assert(lowBits == Math.floor(lowBits)); - jspb.asserts.assert(highBits == Math.floor(highBits)); - jspb.asserts.assert( - (lowBits >= 0) && (lowBits < jspb.BinaryConstants.TWO_TO_32)); - jspb.asserts.assert( - (highBits >= 0) && (highBits < jspb.BinaryConstants.TWO_TO_32)); - this.writeUint32(lowBits); - this.writeUint32(highBits); -}; - -/** - * 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. - * @export - */ -jspb.BinaryEncoder.prototype.writeUnsignedVarint32 = function(value) { - jspb.asserts.assert(value == Math.floor(value)); - jspb.asserts.assert( - (value >= 0) && (value < jspb.BinaryConstants.TWO_TO_32)); - - while (value > 127) { - this.buffer_.push((value & 0x7f) | 0x80); - value = value >>> 7; + /** + * @return {number} + */ + length() { + return this.buffer_.length; } - 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. - * @export - */ -jspb.BinaryEncoder.prototype.writeSignedVarint32 = function(value) { - jspb.asserts.assert(value == Math.floor(value)); - jspb.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; + /** + * @return {!Array} + */ + end() { + const buffer = this.buffer_; + this.buffer_ = []; + return buffer; } - // 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; + /** + * 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. + */ + writeSplitVarint64(lowBits, highBits) { + asserts.assert(lowBits == Math.floor(lowBits)); + asserts.assert(highBits == Math.floor(highBits)); + asserts.assert((lowBits >= 0) && (lowBits < BinaryConstants.TWO_TO_32)); + asserts.assert((highBits >= 0) && (highBits < 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); } - // 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. - * @export - */ -jspb.BinaryEncoder.prototype.writeUnsignedVarint64 = function(value) { - jspb.asserts.assert(value == Math.floor(value)); - jspb.asserts.assert( - (value >= 0) && (value < jspb.BinaryConstants.TWO_TO_64)); - jspb.utils.splitInt64(value); - this.writeSplitVarint64(jspb.utils.getSplit64Low(), jspb.utils.getSplit64High()); -}; - - -/** - * 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. - * @export - */ -jspb.BinaryEncoder.prototype.writeSignedVarint64 = function(value) { - jspb.asserts.assert(value == Math.floor(value)); - jspb.asserts.assert( - (value >= -jspb.BinaryConstants.TWO_TO_63) && - (value < jspb.BinaryConstants.TWO_TO_63)); - jspb.utils.splitInt64(value); - this.writeSplitVarint64(jspb.utils.getSplit64Low(), jspb.utils.getSplit64High()); -}; - - -/** - * 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. - * @export - */ -jspb.BinaryEncoder.prototype.writeZigzagVarint32 = function(value) { - jspb.asserts.assert(value == Math.floor(value)); - jspb.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. - * @export - */ -jspb.BinaryEncoder.prototype.writeZigzagVarint64 = function(value) { - jspb.asserts.assert(value == Math.floor(value)); - jspb.asserts.assert( - (value >= -jspb.BinaryConstants.TWO_TO_63) && - (value < jspb.BinaryConstants.TWO_TO_63)); - jspb.utils.splitZigzag64(value); - this.writeSplitVarint64(jspb.utils.getSplit64Low(), jspb.utils.getSplit64High()); -}; - - -/** - * Encodes a JavaScript decimal string into its wire-format, zigzag-encoded - * varint representation and stores it in the buffer. Integers not representable - * in 64 bits will be truncated. - * @param {string} value The integer to convert. - * @export - */ -jspb.BinaryEncoder.prototype.writeZigzagVarint64String = function(value) { - this.writeZigzagVarintHash64(jspb.utils.decimalStringToHash64(value)); -}; - - -/** - * 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. - * @export - */ -jspb.BinaryEncoder.prototype.writeZigzagVarintHash64 = function(hash) { - var self = this; - jspb.utils.splitHash64(hash); - jspb.utils.toZigzag64( - jspb.utils.getSplit64Low(), jspb.utils.getSplit64High(), 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. - * @export - */ -jspb.BinaryEncoder.prototype.writeUint8 = function(value) { - jspb.asserts.assert(value == Math.floor(value)); - jspb.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. - * @export - */ -jspb.BinaryEncoder.prototype.writeUint16 = function(value) { - jspb.asserts.assert(value == Math.floor(value)); - jspb.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. - * @export - */ -jspb.BinaryEncoder.prototype.writeUint32 = function(value) { - jspb.asserts.assert(value == Math.floor(value)); - jspb.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. - * @export - */ -jspb.BinaryEncoder.prototype.writeUint64 = function(value) { - jspb.asserts.assert(value == Math.floor(value)); - jspb.asserts.assert( - (value >= 0) && (value < jspb.BinaryConstants.TWO_TO_64)); - jspb.utils.splitUint64(value); - this.writeUint32(jspb.utils.getSplit64Low()); - this.writeUint32(jspb.utils.getSplit64High()); -}; - - -/** - * 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. - * @export - */ -jspb.BinaryEncoder.prototype.writeInt8 = function(value) { - jspb.asserts.assert(value == Math.floor(value)); - jspb.asserts.assert((value >= -128) && (value < 128)); - this.buffer_.push((value >>> 0) & 0xFF); -}; + /** + * Encodes a 64-bit integer in 32:32 split representation into its wire-format + * fixed 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. + */ + writeSplitFixed64(lowBits, highBits) { + asserts.assert(lowBits == Math.floor(lowBits)); + asserts.assert(highBits == Math.floor(highBits)); + asserts.assert((lowBits >= 0) && (lowBits < BinaryConstants.TWO_TO_32)); + asserts.assert((highBits >= 0) && (highBits < BinaryConstants.TWO_TO_32)); + this.writeUint32(lowBits); + this.writeUint32(highBits); + } + /** + * Encodes a 64-bit integer in 32:32 split representation into its wire-format + * a zigzag 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. + */ + writeSplitZigzagVarint64(lowBits, highBits) { + utils.toZigzag64(lowBits, highBits, (lo, hi) => { + this.writeSplitVarint64(lo >>> 0, hi >>> 0); + }); + } -/** - * 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. - * @export - */ -jspb.BinaryEncoder.prototype.writeInt16 = function(value) { - jspb.asserts.assert(value == Math.floor(value)); - jspb.asserts.assert((value >= -32768) && (value < 32768)); - this.buffer_.push((value >>> 0) & 0xFF); - this.buffer_.push((value >>> 8) & 0xFF); -}; + /** + * 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. + */ + writeUnsignedVarint32(value) { + asserts.assert(value == Math.floor(value)); + asserts.assert((value >= 0) && (value < BinaryConstants.TWO_TO_32)); + + while (value > 127) { + this.buffer_.push((value & 0x7f) | 0x80); + value = value >>> 7; + } + this.buffer_.push(value); + } -/** - * 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. - * @export - */ -jspb.BinaryEncoder.prototype.writeInt32 = function(value) { - jspb.asserts.assert(value == Math.floor(value)); - jspb.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); -}; + /** + * 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. + */ + writeSignedVarint32(value) { + asserts.assert(value == Math.floor(value)); + asserts.assert( + (value >= -BinaryConstants.TWO_TO_31) && + (value < 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 (let i = 0; i < 9; i++) { + this.buffer_.push((value & 0x7f) | 0x80); + value = value >> 7; + } -/** - * 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. - * @export - */ -jspb.BinaryEncoder.prototype.writeInt64 = function(value) { - jspb.asserts.assert(value == Math.floor(value)); - jspb.asserts.assert( - (value >= -jspb.BinaryConstants.TWO_TO_63) && - (value < jspb.BinaryConstants.TWO_TO_63)); - jspb.utils.splitInt64(value); - this.writeSplitFixed64(jspb.utils.getSplit64Low(), jspb.utils.getSplit64High()); -}; + // 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. + */ + writeUnsignedVarint64(value) { + asserts.assert(value == Math.floor(value)); + asserts.assert((value >= 0) && (value < BinaryConstants.TWO_TO_64)); + utils.splitInt64(value); + this.writeSplitVarint64(utils.getSplit64Low(), utils.getSplit64High()); + } -/** - * Writes a 64-bit integer decimal strings to the buffer. Numbers outside the - * range [-2^63,2^63) will be truncated. - * @param {string} value The value to write. - * @export - */ -jspb.BinaryEncoder.prototype.writeInt64String = function(value) { - jspb.asserts.assert(value == Math.floor(value)); - jspb.asserts.assert( - (+value >= -jspb.BinaryConstants.TWO_TO_63) && - (+value < jspb.BinaryConstants.TWO_TO_63)); - jspb.utils.splitHash64(jspb.utils.decimalStringToHash64(value)); - this.writeSplitFixed64(jspb.utils.getSplit64Low(), jspb.utils.getSplit64High()); -}; + /** + * 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. + */ + writeSignedVarint64(value) { + asserts.assert(value == Math.floor(value)); + asserts.assert( + (value >= -BinaryConstants.TWO_TO_63) && + (value < BinaryConstants.TWO_TO_63)); + utils.splitInt64(value); + this.writeSplitVarint64(utils.getSplit64Low(), utils.getSplit64High()); + } + /** + * 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. + */ + writeZigzagVarint32(value) { + asserts.assert(value == Math.floor(value)); + asserts.assert( + (value >= -BinaryConstants.TWO_TO_31) && + (value < BinaryConstants.TWO_TO_31)); + this.writeUnsignedVarint32(utils.toZigzag32(value)); + } -/** - * 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. - * @export - */ -jspb.BinaryEncoder.prototype.writeFloat = function(value) { - jspb.asserts.assert( - value === Infinity || value === -Infinity || isNaN(value) || - ((value >= -jspb.BinaryConstants.FLOAT32_MAX) && - (value <= jspb.BinaryConstants.FLOAT32_MAX))); - jspb.utils.splitFloat32(value); - this.writeUint32(jspb.utils.getSplit64Low()); -}; + /** + * 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. + */ + writeZigzagVarint64(value) { + asserts.assert(value == Math.floor(value)); + asserts.assert( + (value >= -BinaryConstants.TWO_TO_63) && + (value < BinaryConstants.TWO_TO_63)); + utils.splitZigzag64(value); + this.writeSplitVarint64(utils.getSplit64Low(), utils.getSplit64High()); + } + /** + * Encodes a JavaScript decimal string into its wire-format, zigzag-encoded + * varint representation and stores it in the buffer. Integers not + * representable in 64 bits will be truncated. + * @param {string} value The integer to convert. + */ + writeZigzagVarint64String(value) { + utils.splitDecimalString(value); + utils.toZigzag64( + utils.getSplit64Low(), utils.getSplit64High(), (lo, hi) => { + this.writeSplitVarint64(lo >>> 0, hi >>> 0); + }); + } -/** - * 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. - * @export - */ -jspb.BinaryEncoder.prototype.writeDouble = function(value) { - jspb.asserts.assert( - value === Infinity || value === -Infinity || isNaN(value) || - ((value >= -jspb.BinaryConstants.FLOAT64_MAX) && - (value <= jspb.BinaryConstants.FLOAT64_MAX))); - jspb.utils.splitFloat64(value); - this.writeUint32(jspb.utils.getSplit64Low()); - this.writeUint32(jspb.utils.getSplit64High()); -}; + /** + * 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. + */ + writeUint8(value) { + asserts.assert(value == Math.floor(value)); + 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. + */ + writeUint16(value) { + asserts.assert(value == Math.floor(value)); + asserts.assert((value >= 0) && (value < 65536)); + this.buffer_.push((value >>> 0) & 0xFF); + this.buffer_.push((value >>> 8) & 0xFF); + } -/** - * Writes a boolean value to the buffer as a varint. We allow numbers as input - * because the JSPB code generator uses 0/1 instead of true/false to save space - * in the string representation of the proto. - * @param {boolean|number} value The value to write. - * @export - */ -jspb.BinaryEncoder.prototype.writeBool = function(value) { - jspb.asserts.assert( - typeof value === 'boolean' || typeof value === 'number'); - this.buffer_.push(value ? 1 : 0); -}; + /** + * 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. + */ + writeUint32(value) { + asserts.assert(value == Math.floor(value)); + asserts.assert((value >= 0) && (value < 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. + */ + writeUint64(value) { + asserts.assert(value == Math.floor(value)); + asserts.assert((value >= 0) && (value < BinaryConstants.TWO_TO_64)); + utils.splitUint64(value); + this.writeUint32(utils.getSplit64Low()); + this.writeUint32(utils.getSplit64High()); + } -/** - * Writes an enum value to the buffer as a varint. - * @param {number} value The value to write. - * @export - */ -jspb.BinaryEncoder.prototype.writeEnum = function(value) { - jspb.asserts.assert(value == Math.floor(value)); - jspb.asserts.assert( - (value >= -jspb.BinaryConstants.TWO_TO_31) && - (value < jspb.BinaryConstants.TWO_TO_31)); - this.writeSignedVarint32(value); -}; + /** + * 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. + */ + writeInt8(value) { + asserts.assert(value == Math.floor(value)); + 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. + */ + writeInt16(value) { + asserts.assert(value == Math.floor(value)); + asserts.assert((value >= -32768) && (value < 32768)); + this.buffer_.push((value >>> 0) & 0xFF); + this.buffer_.push((value >>> 8) & 0xFF); + } -/** - * Writes an arbitrary byte array to the buffer. - * @param {!Uint8Array} bytes The array of bytes to write. - * @export - */ -jspb.BinaryEncoder.prototype.writeBytes = function(bytes) { - this.buffer_.push.apply(this.buffer_, bytes); -}; + /** + * 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. + */ + writeInt32(value) { + asserts.assert(value == Math.floor(value)); + asserts.assert( + (value >= -BinaryConstants.TWO_TO_31) && + (value < 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. + */ + writeInt64(value) { + asserts.assert(value == Math.floor(value)); + asserts.assert( + (value >= -BinaryConstants.TWO_TO_63) && + (value < BinaryConstants.TWO_TO_63)); + utils.splitInt64(value); + this.writeSplitFixed64(utils.getSplit64Low(), utils.getSplit64High()); + } -/** - * 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. - * @export - */ -jspb.BinaryEncoder.prototype.writeVarintHash64 = function(hash) { - jspb.utils.splitHash64(hash); - this.writeSplitVarint64(jspb.utils.getSplit64Low(), jspb.utils.getSplit64High()); -}; + /** + * Writes a single-precision floating point value to the buffer. Numbers + * requiring more than 32 bits of precision will be truncated. + * @param {number|string} value The value to write, accepts + * 'Infinity'/'-Infinity'/'NaN' for JSPB wire format compatibility. + */ + writeFloat(value) { + asserts.assert( + // Explicitly using == to accept strings + (value == Infinity || value == -Infinity || isNaN(value) || + (typeof value === 'number' && + (value >= -BinaryConstants.FLOAT32_MAX) && + (value <= BinaryConstants.FLOAT32_MAX)))); + utils.splitFloat32(value); + this.writeUint32(utils.getSplit64Low()); + } + /** + * 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|string} value The value to write, accepts + * 'Infinity'/'-Infinity'/'NaN' for JSPB wire format compatibility. + */ + writeDouble(value) { + asserts.assert( + typeof value === 'number' || value === 'Infinity' || + value === '-Infinity' || value === 'NaN'); + utils.splitFloat64(value); + this.writeUint32(utils.getSplit64Low()); + this.writeUint32(utils.getSplit64High()); + } -/** - * 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. - * @export - */ -jspb.BinaryEncoder.prototype.writeFixedHash64 = function(hash) { - jspb.utils.splitHash64(hash); - this.writeUint32(jspb.utils.getSplit64Low()); - this.writeUint32(jspb.utils.getSplit64High()); -}; + /** + * Writes a boolean value to the buffer as a varint. We allow numbers as input + * because the JSPB code generator uses 0/1 instead of true/false to save + * space in the string representation of the proto. + * @param {boolean|number} value The value to write. + */ + writeBool(value) { + asserts.assert(typeof value === 'boolean' || typeof value === 'number'); + this.buffer_.push(value ? 1 : 0); + } + /** + * Writes an enum value to the buffer as a varint. + * @param {number} value The value to write. + */ + writeEnum(value) { + asserts.assert(value == Math.floor(value)); + asserts.assert( + (value >= -BinaryConstants.TWO_TO_31) && + (value < BinaryConstants.TWO_TO_31)); + this.writeSignedVarint32(value); + } -/** - * 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. - * @export - */ -jspb.BinaryEncoder.prototype.writeString = function(value) { - var oldLength = this.buffer_.length; - - // Protect against non-string values being silently ignored. - jspb.asserts.assertString(value); - - 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 if (c < 65536) { - // Look for surrogates - if (c >= 0xD800 && c <= 0xDBFF && i + 1 < value.length) { - var second = value.charCodeAt(i + 1); - if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate - // http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae - c = (c - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; - - this.buffer_.push((c >> 18) | 240); - this.buffer_.push(((c >> 12) & 63) | 128); - this.buffer_.push(((c >> 6) & 63) | 128); - this.buffer_.push((c & 63) | 128); - i++; - } - } else { - this.buffer_.push((c >> 12) | 224); - this.buffer_.push(((c >> 6) & 63) | 128); - this.buffer_.push((c & 63) | 128); - } + /** + * Writes a byte array to our buffer. + * @param {!Uint8Array} bytes The array of bytes to write. + */ + writeBytes(bytes) { + // avoid a stackoverflow on large arrays. + while (bytes.length > MAX_PUSH) { + Array.prototype.push.apply(this.buffer_, bytes.subarray(0, MAX_PUSH)); + bytes = bytes.subarray(MAX_PUSH); } + Array.prototype.push.apply(this.buffer_, bytes); } +} - var length = this.buffer_.length - oldLength; - return length; -}; +exports = {BinaryEncoder}; diff --git a/binary/encoder_alias.js b/binary/encoder_alias.js new file mode 100755 index 0000000..187507f --- /dev/null +++ b/binary/encoder_alias.js @@ -0,0 +1,9 @@ +/** + * @fileoverview Legacy alias for the old namespace used by encoder.js + */ +goog.module('jspb.BinaryEncoder'); +goog.module.declareLegacyNamespace(); + +const {BinaryEncoder} = goog.require('jspb.binary.encoder'); + +exports = BinaryEncoder; diff --git a/binary/errors.js b/binary/errors.js new file mode 100755 index 0000000..dbd6d64 --- /dev/null +++ b/binary/errors.js @@ -0,0 +1,122 @@ +/** + * @fileoverview Standard error messages for errors detected when parsing + * binary protos. + */ +goog.module('jspb.binary.errors'); + +// N.B. In the functions below invoke the `Error` constructor directly in order +// to be compatible with the string replacement semantics in the JsCompiler, +// which typically matches on strings passed to the Error constructor. + +/** + * Reports that we didn't read the number of expected bytes for a message. + * @return {!Error} + */ +function messageLengthMismatchError( + /** number */ messageLength, /** number */ readLength) { + // NOTE: we directly throw here instead of report because this is what we used + // to do as well. + return new Error( + `Message parsing ended unexpectedly. Expected to read ` + + `${messageLength} bytes, instead read ${readLength} bytes, either the ` + + `data ended unexpectedly or the message misreported its own length`); +} + + +/** + * Reports an invalid wire type value. + * + * @return {!Error} + */ +function invalidWireTypeError(/** number */ wireType, /** number */ position) { + return new Error(`Invalid wire type: ${wireType} (at position ${position})`); +} + +/** + * Reports an invalid field number. + * + * @return {!Error} + */ +function invalidFieldNumberError( + /** number */ fieldNumber, /** number */ position) { + return new Error( + `Invalid field number: ${fieldNumber} (at position ${position})`); +} + +/** + * Reports message-set parsing faield + * @return {!Error} + */ +function malformedBinaryBytesForMessageSet() { + return new Error('Malformed binary bytes for message set'); +} + +/** + * Reports a failure to find an END_GROUP tag because we hit end of stream. + * + * @return {!Error} + */ +function unmatchedStartGroupEofError() { + return new Error('Unmatched start-group tag: stream EOF'); +} + +/** + * Reports a general failure to find an END_GROUP tag matching a START_GROUP. + * + * @return {!Error} + */ +function unmatchedStartGroupError() { + return new Error('Unmatched end-group tag'); +} + +/** + * Reports that parsing a group did not end on an END_GROUP tag. + * + * @return {!Error} + */ +function groupDidNotEndWithEndGroupError() { + return new Error('Group submessage did not end with an END_GROUP tag'); +} + +/** + * Reports that the varint is invalid in some way. + * + * @return {!Error} + */ +function invalidVarintError() { + return new Error('Failed to read varint, encoding is invalid.'); +} + +/** + * Reports that we read more bytes than were available. + * + * @return {!Error} + */ +function readTooFarError( + /** number */ expectedLength, /** number */ readLength) { + return new Error(`Tried to read past the end of the data ${readLength} > ${ + expectedLength}`); +} + +/** + * Reports that we read more bytes than were available. + * + * @return {!Error} + */ +function negativeByteLengthError( + /** number */ length) { + return new Error(`Tried to read a negative byte length: ${length}`); +} + +exports = { + messageLengthMismatchError, + groupDidNotEndWithEndGroupError, + invalidFieldNumberError, + invalidVarintError, + invalidWireTypeError, + malformedBinaryBytesForMessageSet, + negativeByteLengthError, + readTooFarError, + unmatchedStartGroupError, + unmatchedStartGroupEofError, +}; diff --git a/binary/internal_buffer.js b/binary/internal_buffer.js new file mode 100755 index 0000000..5bb2d88 --- /dev/null +++ b/binary/internal_buffer.js @@ -0,0 +1,96 @@ +/** + * @fileoverview Internal utilities for working with buffers. + * @package + * + * DO NOT USE THIS OUTSIDE OF THIS PACKAGE. + */ +goog.module('jspb.binary.internal_buffer'); + +const {ByteSource} = goog.require('jspb.binary.bytesource'); +const {ByteString} = goog.require('jspb.bytestring'); +const {decodeByteArray} = goog.require('jspb.internal_bytes'); +const {unsafeByteStringFromUint8Array, unsafeUint8ArrayFromByteString} = goog.require('jspb.unsafe_bytestring'); + + +class Buffer { + constructor(/** !Uint8Array */ buffer, /** boolean */ isImmutable, + /** !ByteString= */ maybeByteString) { + /** @const {!Uint8Array}*/ + this.buffer = buffer; + + /** @private {!ByteString|undefined} */ + this.bufferAsByteStringInternal = maybeByteString; + if (maybeByteString && !isImmutable) { + throw goog.DEBUG ? + new Error('Buffer must be immutable if a ByteString is provided.') : + new Error(); + } + + /** + * Whether our input must be immutable and we cannot allow callers to + * mutate it. + * + * @const {boolean} + */ + this.isImmutable = isImmutable; + } + + /** @return {?ByteString} */ + getBufferAsByteStringIfImmutable() { + if (!this.isImmutable) { + throw goog.DEBUG ? + new Error('Cannot get ByteString from mutable buffer.') : + new Error(); + } + if (this.buffer == null) return null; + return this.bufferAsByteStringInternal ??= + unsafeByteStringFromUint8Array(this.buffer); + } +} + +/** + * Converts any type defined in ByteSource into a Uint8Array. + * @param {!ByteSource|!ByteString} data + * @param {boolean} treatNewDataAsImmutable Whether to treat new data as + * immutable. + * @return {!Buffer} a tuple of the data and + * whether or not this is an immutable reference + */ +function bufferFromSource(data, treatNewDataAsImmutable) { + if (typeof data === 'string') { + return new Buffer(decodeByteArray(data), treatNewDataAsImmutable); + } else if (Array.isArray(data)) { + return new Buffer(new Uint8Array(data), treatNewDataAsImmutable); + } else if (data.constructor === Uint8Array) { + const u8 = /** @type {!Uint8Array}*/ (data); + return new Buffer(u8, /* isImmutable= */ false); + } else if (data.constructor === ArrayBuffer) { + const u8 = new Uint8Array(/** @type {!ArrayBuffer} */ (data)); + return new Buffer(u8, /* isImmutable= */ false); + } else if (data.constructor === ByteString) { + const byteString = /** @type {!ByteString} */ (data); + const u8 = unsafeUint8ArrayFromByteString(byteString); + return new Buffer(u8, /* isImmutable= */ true, byteString); + } else if (data instanceof Uint8Array) { + // If this is a node subclass, wrap a new Uint8Array around the buffer to + // ensure jspb code only ever deals with Uint8Array exactly to ensure + // monomorphism. + const u8 = (data.constructor === Uint8Array) ? + data : + new Uint8Array(data.buffer, data.byteOffset, data.byteLength); + return new Buffer(u8, /* isImmutable= */ false); + } else { + throw goog.DEBUG ? + new Error( + 'Type not convertible to a Uint8Array, expected a Uint8Array, an ' + + 'ArrayBuffer, a base64 encoded string, a ByteString or an Array of ' + + 'numbers') : + new Error(); + } +} + + +exports = { + Buffer, + bufferFromSource, +}; diff --git a/binary/message_test.js b/binary/message_test.js old mode 100644 new mode 100755 diff --git a/binary/proto_test.js b/binary/proto_test.js old mode 100644 new mode 100755 index 6720cec..608d6d0 --- a/binary/proto_test.js +++ b/binary/proto_test.js @@ -91,7 +91,9 @@ goog.require('proto.jspb.test.extendRepeatedUint64List'); // clang-format off // CommonJS-LoadFromFile: ../node_modules/google-protobuf/google/protobuf/any_pb proto.google.protobuf goog.require('proto.google.protobuf.Any'); +// clang-format on +const BinaryWriter = goog.module.get('jspb.BinaryWriter'); const suite = {}; @@ -108,15 +110,15 @@ function fillAllFields(msg) { msg.setOptionalInt32(-42); // can be exactly represented by JS number (64-bit double, i.e., 52-bit // mantissa). - msg.setOptionalInt64(-0x7fffffff00000000); + msg.setOptionalInt64(String(-BigInt('0x7fffffff00000000'))); msg.setOptionalUint32(0x80000000); - msg.setOptionalUint64(0xf000000000000000); + msg.setOptionalUint64(String(BigInt('0xf000000000000000'))); msg.setOptionalSint32(-100); - msg.setOptionalSint64(-0x8000000000000000); + msg.setOptionalSint64(String(-BigInt('0x8000000000000000'))); msg.setOptionalFixed32(1234); - msg.setOptionalFixed64(0x1234567800000000); + msg.setOptionalFixed64(String(BigInt('0x1234567800000000'))); msg.setOptionalSfixed32(-1234); - msg.setOptionalSfixed64(-0x1234567800000000); + msg.setOptionalSfixed64(String(-BigInt('0x1234567800000000'))); msg.setOptionalFloat(1.5); msg.setOptionalDouble(-1.5); msg.setOptionalBool(true); @@ -132,15 +134,15 @@ function fillAllFields(msg) { msg.setRepeatedInt32List([-42]); - msg.setRepeatedInt64List([-0x7fffffff00000000]); + msg.setRepeatedInt64List([String(-BigInt('0x7fffffff00000000'))]); msg.setRepeatedUint32List([0x80000000]); - msg.setRepeatedUint64List([0xf000000000000000]); + msg.setRepeatedUint64List([String(BigInt('0xf000000000000000'))]); msg.setRepeatedSint32List([-100]); - msg.setRepeatedSint64List([-0x8000000000000000]); + msg.setRepeatedSint64List([String(-BigInt('0x8000000000000000'))]); msg.setRepeatedFixed32List([1234]); - msg.setRepeatedFixed64List([0x1234567800000000]); + msg.setRepeatedFixed64List([String(BigInt('0x1234567800000000'))]); msg.setRepeatedSfixed32List([-1234]); - msg.setRepeatedSfixed64List([-0x1234567800000000]); + msg.setRepeatedSfixed64List([String(-BigInt('0x1234567800000000'))]); msg.setRepeatedFloatList([1.5]); msg.setRepeatedDoubleList([-1.5]); msg.setRepeatedBoolList([true]); @@ -154,15 +156,15 @@ function fillAllFields(msg) { msg.setRepeatedForeignEnumList([proto.jspb.test.ForeignEnum.FOREIGN_FOO]); msg.setPackedRepeatedInt32List([-42]); - msg.setPackedRepeatedInt64List([-0x7fffffff00000000]); + msg.setPackedRepeatedInt64List([String(-BigInt('0x7fffffff00000000'))]); msg.setPackedRepeatedUint32List([0x80000000]); - msg.setPackedRepeatedUint64List([0xf000000000000000]); + msg.setPackedRepeatedUint64List([String(BigInt('0xf000000000000000'))]); msg.setPackedRepeatedSint32List([-100]); - msg.setPackedRepeatedSint64List([-0x8000000000000000]); + msg.setPackedRepeatedSint64List([String(-BigInt('0x8000000000000000'))]); msg.setPackedRepeatedFixed32List([1234]); - msg.setPackedRepeatedFixed64List([0x1234567800000000]); + msg.setPackedRepeatedFixed64List([String(BigInt('0x1234567800000000'))]); msg.setPackedRepeatedSfixed32List([-1234]); - msg.setPackedRepeatedSfixed64List([-0x1234567800000000]); + msg.setPackedRepeatedSfixed64List([String(-BigInt('0x1234567800000000'))]); msg.setPackedRepeatedFloatList([1.5]); msg.setPackedRepeatedDoubleList([-1.5]); msg.setPackedRepeatedBoolList([true]); @@ -199,15 +201,15 @@ function bytesCompare(arr, expected) { */ function checkAllFields(original, copy) { expect(copy.getOptionalInt32()).toEqual(-42); - expect(copy.getOptionalInt64()).toEqual(-0x7fffffff00000000); + expect(copy.getOptionalInt64()).toEqual(String(-BigInt('0x7fffffff00000000'))); expect(copy.getOptionalUint32()).toEqual(0x80000000); - expect(copy.getOptionalUint64()).toEqual(0xf000000000000000); + expect(copy.getOptionalUint64()).toEqual(String(BigInt('0xf000000000000000'))); expect(copy.getOptionalSint32()).toEqual(-100); - expect(copy.getOptionalSint64()).toEqual(-0x8000000000000000); + expect(copy.getOptionalSint64()).toEqual(String(-BigInt('0x8000000000000000'))); expect(copy.getOptionalFixed32()).toEqual(1234); - expect(copy.getOptionalFixed64()).toEqual(0x1234567800000000); + expect(copy.getOptionalFixed64()).toEqual(String(BigInt('0x1234567800000000'))); expect(copy.getOptionalSfixed32()).toEqual(-1234); - expect(copy.getOptionalSfixed64()).toEqual(-0x1234567800000000); + expect(copy.getOptionalSfixed64()).toEqual(String(-BigInt('0x1234567800000000'))); expect(copy.getOptionalFloat()).toEqual(1.5); expect(copy.getOptionalDouble()).toEqual(-1.5); expect(copy.getOptionalBool()).toBeTrue(); @@ -215,38 +217,41 @@ function checkAllFields(original, copy) { expect(bytesCompare(copy.getOptionalBytes(), BYTES)).toEqual(true); expect(true).toEqual(bytesCompare(copy.getOptionalBytes_asU8(), BYTES)); expect(copy.getOptionalBytes_asB64()) - .toEqual(goog.crypt.base64.encodeByteArray(BYTES)); + .toEqual(goog.crypt.base64.encodeByteArray(BYTES)); expect(copy.getOptionalGroup().getA()).toEqual(100); expect(copy.getOptionalForeignMessage().getC()).toEqual(16); expect(proto.jspb.test.ForeignEnum.FOREIGN_FOO) - .toEqual(copy.getOptionalForeignEnum()); + .toEqual(copy.getOptionalForeignEnum()); expect(copy.getOneofString()).toEqual('oneof'); expect(proto.jspb.test.TestAllTypes.OneofFieldCase.ONEOF_STRING) - .toEqual(copy.getOneofFieldCase()); + .toEqual(copy.getOneofFieldCase()); expect(copy.getRepeatedInt32List()).toEqual([-42]); - expect(copy.getRepeatedInt64List()).toEqual([-0x7fffffff00000000]); + expect(copy.getRepeatedInt64List()) + .toEqual([String(-BigInt('0x7fffffff00000000'))]); expect(copy.getRepeatedUint32List()).toEqual([0x80000000]); - expect(copy.getRepeatedUint64List()).toEqual([0xf000000000000000]); + expect(copy.getRepeatedUint64List()) + .toEqual([String(BigInt('0xf000000000000000'))]); expect(copy.getRepeatedSint32List()).toEqual([-100]); - expect(copy.getRepeatedSint64List()).toEqual([-0x8000000000000000]); + expect(copy.getRepeatedSint64List()) + .toEqual([String(-BigInt('0x8000000000000000'))]); expect(copy.getRepeatedFixed32List()).toEqual([1234]); - expect(copy.getRepeatedFixed64List()).toEqual([0x1234567800000000]); + expect(copy.getRepeatedFixed64List()).toEqual([String(BigInt('0x1234567800000000'))]); expect(copy.getRepeatedSfixed32List()).toEqual([-1234]); - expect(copy.getRepeatedSfixed64List()).toEqual([-0x1234567800000000]); + expect(copy.getRepeatedSfixed64List()).toEqual([String(-BigInt('0x1234567800000000'))]); expect(copy.getRepeatedFloatList()).toEqual([1.5]); expect(copy.getRepeatedDoubleList()).toEqual([-1.5]); expect(copy.getRepeatedBoolList()).toEqual([true]); expect(copy.getRepeatedStringList()).toEqual(['hello world']); expect(copy.getRepeatedBytesList().length).toEqual(2); expect(true).toEqual( - bytesCompare(copy.getRepeatedBytesList_asU8()[0], BYTES)); + bytesCompare(copy.getRepeatedBytesList_asU8()[0], BYTES)); expect(true).toEqual(bytesCompare(copy.getRepeatedBytesList()[0], BYTES)); expect(true).toEqual( - bytesCompare(copy.getRepeatedBytesList_asU8()[1], BYTES)); + bytesCompare(copy.getRepeatedBytesList_asU8()[1], BYTES)); expect(copy.getRepeatedBytesList_asB64()[0]).toEqual(BYTES_B64); expect(copy.getRepeatedBytesList_asB64()[1]).toEqual(BYTES_B64); expect(copy.getRepeatedGroupList().length).toEqual(1); @@ -258,15 +263,18 @@ function checkAllFields(original, copy) { ]).toEqual(copy.getRepeatedForeignEnumList()); expect(copy.getPackedRepeatedInt32List()).toEqual([-42]); - expect(copy.getPackedRepeatedInt64List()).toEqual([-0x7fffffff00000000]); + expect(copy.getPackedRepeatedInt64List()) + .toEqual([String(-BigInt('0x7fffffff00000000'))]); expect(copy.getPackedRepeatedUint32List()).toEqual([0x80000000]); - expect(copy.getPackedRepeatedUint64List()).toEqual([0xf000000000000000]); + expect(copy.getPackedRepeatedUint64List()) + .toEqual([String(BigInt('0xf000000000000000'))]); expect(copy.getPackedRepeatedSint32List()).toEqual([-100]); - expect(copy.getPackedRepeatedSint64List()).toEqual([-0x8000000000000000]); + expect(copy.getPackedRepeatedSint64List()) + .toEqual([String(-BigInt('0x8000000000000000'))]); expect(copy.getPackedRepeatedFixed32List()).toEqual([1234]); - expect(copy.getPackedRepeatedFixed64List()).toEqual([0x1234567800000000]); + expect(copy.getPackedRepeatedFixed64List()).toEqual([String(BigInt('0x1234567800000000'))]); expect(copy.getPackedRepeatedSfixed32List()).toEqual([-1234]); - expect(copy.getPackedRepeatedSfixed64List()).toEqual([-0x1234567800000000]); + expect(copy.getPackedRepeatedSfixed64List()).toEqual([String(-BigInt('0x1234567800000000'))]); expect(copy.getPackedRepeatedFloatList()).toEqual([1.5]); expect(copy.getPackedRepeatedDoubleList()).toEqual([-1.5]); @@ -282,65 +290,63 @@ function checkAllFields(original, copy) { */ function checkExtensions(msg) { expect(0).toEqual(msg.getExtension(proto.jspb.test.extendOptionalInt32)); - expect(-0x7fffffff00000000) - .toEqual(msg.getExtension(proto.jspb.test.extendOptionalInt64)); + expect(String(-BigInt('0x7fffffff00000000'))) + .toEqual(msg.getExtension(proto.jspb.test.extendOptionalInt64)); expect(0x80000000) - .toEqual(msg.getExtension(proto.jspb.test.extendOptionalUint32)); - expect(0xf000000000000000) - .toEqual(msg.getExtension(proto.jspb.test.extendOptionalUint64)); + .toEqual(msg.getExtension(proto.jspb.test.extendOptionalUint32)); + expect(String(BigInt('0xf000000000000000'))) + .toEqual(msg.getExtension(proto.jspb.test.extendOptionalUint64)); expect(-100).toEqual(msg.getExtension(proto.jspb.test.extendOptionalSint32)); - expect(-0x8000000000000000) - .toEqual(msg.getExtension(proto.jspb.test.extendOptionalSint64)); + expect(String(-BigInt('0x8000000000000000'))) + .toEqual(msg.getExtension(proto.jspb.test.extendOptionalSint64)); expect(1234).toEqual(msg.getExtension(proto.jspb.test.extendOptionalFixed32)); - expect(0x1234567800000000) - .toEqual(msg.getExtension(proto.jspb.test.extendOptionalFixed64)); + expect(String(BigInt('0x1234567800000000'))) + .toEqual(msg.getExtension(proto.jspb.test.extendOptionalFixed64)); expect(-1234).toEqual( - msg.getExtension(proto.jspb.test.extendOptionalSfixed32)); - expect(-0x1234567800000000) - .toEqual(msg.getExtension(proto.jspb.test.extendOptionalSfixed64)); + msg.getExtension(proto.jspb.test.extendOptionalSfixed32)); + expect(String(-BigInt('0x1234567800000000'))) + .toEqual(msg.getExtension(proto.jspb.test.extendOptionalSfixed64)); expect(msg.getExtension(proto.jspb.test.extendOptionalFloat)).toEqual(1.5); expect(msg.getExtension(proto.jspb.test.extendOptionalDouble)).toEqual(-1.5); expect(msg.getExtension(proto.jspb.test.extendOptionalBool)).toEqual(true); expect(msg.getExtension(proto.jspb.test.extendOptionalString)) - .toEqual('hello world'); + .toEqual('hello world'); expect(bytesCompare( - msg.getExtension(proto.jspb.test.extendOptionalBytes), BYTES)) - .toEqual(true); + msg.getExtension(proto.jspb.test.extendOptionalBytes), BYTES)) + .toEqual(true); expect(msg.getExtension(proto.jspb.test.ExtendsWithMessage.optionalExtension) - .getFoo()) - .toEqual(16); + .getFoo()) + .toEqual(16); expect(msg.getExtension(proto.jspb.test.extendRepeatedInt32List)).toEqual([ -42 ]); expect(msg.getExtension(proto.jspb.test.extendRepeatedInt64List)).toEqual([ - -0x7fffffff00000000 + String(-BigInt('0x7fffffff00000000')) ]); expect(msg.getExtension(proto.jspb.test.extendRepeatedUint32List)).toEqual([ 0x80000000 ]); expect(msg.getExtension(proto.jspb.test.extendRepeatedUint64List)).toEqual([ - 0xf000000000000000 + String(BigInt('0xf000000000000000')) ]); expect(msg.getExtension(proto.jspb.test.extendRepeatedSint32List)).toEqual([ -100 ]); expect(msg.getExtension(proto.jspb.test.extendRepeatedSint64List)).toEqual([ - -0x8000000000000000 + String(-BigInt('0x8000000000000000')) ]); expect(msg.getExtension(proto.jspb.test.extendRepeatedFixed32List)).toEqual([ 1234 ]); - expect(msg.getExtension(proto.jspb.test.extendRepeatedFixed64List)).toEqual([ - 0x1234567800000000 - ]); + expect(msg.getExtension(proto.jspb.test.extendRepeatedFixed64List)) + .toEqual([String(BigInt('0x1234567800000000'))]); expect(msg.getExtension(proto.jspb.test.extendRepeatedSfixed32List)).toEqual([ -1234 ]); - expect(msg.getExtension(proto.jspb.test.extendRepeatedSfixed64List)).toEqual([ - -0x1234567800000000 - ]); + expect(msg.getExtension(proto.jspb.test.extendRepeatedSfixed64List)) + .toEqual([String(-BigInt('0x1234567800000000'))]); expect(msg.getExtension(proto.jspb.test.extendRepeatedFloatList)).toEqual([ 1.5 ]); @@ -354,45 +360,45 @@ function checkExtensions(msg) { 'hello world' ]); expect(true).toEqual(bytesCompare( - msg.getExtension(proto.jspb.test.extendRepeatedBytesList)[0], BYTES)); + msg.getExtension(proto.jspb.test.extendRepeatedBytesList)[0], BYTES)); expect(1000).toEqual( - msg.getExtension( - proto.jspb.test.ExtendsWithMessage.repeatedExtensionList)[0] - .getFoo()); + msg.getExtension( + proto.jspb.test.ExtendsWithMessage.repeatedExtensionList)[0] + .getFoo()); expect([ proto.jspb.test.ForeignEnum.FOREIGN_FOO ]).toEqual(msg.getExtension(proto.jspb.test.extendRepeatedForeignEnumList)); expect(msg.getExtension(proto.jspb.test.extendPackedRepeatedInt32List)) - .toEqual([-42]); + .toEqual([-42]); expect(msg.getExtension(proto.jspb.test.extendPackedRepeatedInt64List)) - .toEqual([-0x7fffffff00000000]); + .toEqual([String(-BigInt('0x7fffffff00000000'))]); expect(msg.getExtension(proto.jspb.test.extendPackedRepeatedUint32List)) - .toEqual([0x80000000]); + .toEqual([0x80000000]); expect(msg.getExtension(proto.jspb.test.extendPackedRepeatedUint64List)) - .toEqual([0xf000000000000000]); + .toEqual([String(BigInt('0xf000000000000000'))]); expect(msg.getExtension(proto.jspb.test.extendPackedRepeatedSint32List)) - .toEqual([-100]); + .toEqual([-100]); expect(msg.getExtension(proto.jspb.test.extendPackedRepeatedSint64List)) - .toEqual([-0x8000000000000000]); + .toEqual([String(-BigInt('0x8000000000000000'))]); expect(msg.getExtension(proto.jspb.test.extendPackedRepeatedFixed32List)) - .toEqual([1234]); + .toEqual([1234]); expect(msg.getExtension(proto.jspb.test.extendPackedRepeatedFixed64List)) - .toEqual([0x1234567800000000]); + .toEqual([String(BigInt('0x1234567800000000'))]); expect(msg.getExtension(proto.jspb.test.extendPackedRepeatedSfixed32List)) - .toEqual([-1234]); + .toEqual([-1234]); expect(msg.getExtension(proto.jspb.test.extendPackedRepeatedSfixed64List)) - .toEqual([-0x1234567800000000]); + .toEqual([String(-BigInt('0x1234567800000000'))]); expect(msg.getExtension(proto.jspb.test.extendPackedRepeatedFloatList)) - .toEqual([1.5]); + .toEqual([1.5]); expect(msg.getExtension(proto.jspb.test.extendPackedRepeatedDoubleList)) - .toEqual([-1.5]); + .toEqual([-1.5]); expect(msg.getExtension(proto.jspb.test.extendPackedRepeatedBoolList)) - .toEqual([true]); + .toEqual([true]); expect([proto.jspb.test.ForeignEnum.FOREIGN_FOO]) - .toEqual(msg.getExtension( - proto.jspb.test.extendPackedRepeatedForeignEnumList)); + .toEqual(msg.getExtension( + proto.jspb.test.extendPackedRepeatedForeignEnumList)); } @@ -460,20 +466,20 @@ describe('protoBinaryTest', () => { expect(typeof msg.getRepeatedBytesList_asB64()[0]).toEqual('string'); expect(typeof msg.getRepeatedBytesList_asB64()[1]).toEqual('string'); expect(msg.getRepeatedBytesList_asU8()[0] instanceof Uint8Array) - .toBeTrue(); + .toBeTrue(); expect(msg.getRepeatedBytesList_asU8()[1] instanceof Uint8Array) - .toBeTrue(); + .toBeTrue(); expect(bytesCompare(msg.getRepeatedBytesList()[0], BYTES)).toBeTrue(); expect(bytesCompare(msg.getRepeatedBytesList()[1], BYTES)).toBeTrue(); expect(bytesCompare(msg.getRepeatedBytesList_asB64()[0], BYTES)) - .toBeTrue(); + .toBeTrue(); expect(bytesCompare(msg.getRepeatedBytesList_asB64()[1], BYTES)) - .toBeTrue(); + .toBeTrue(); expect(bytesCompare(msg.getRepeatedBytesList_asU8()[0], BYTES)) - .toBeTrue(); + .toBeTrue(); expect(bytesCompare(msg.getRepeatedBytesList_asU8()[1], BYTES)) - .toBeTrue(); + .toBeTrue(); } msg.setRepeatedBytesList([BYTES, BYTES]); assertGetters(); @@ -493,16 +499,21 @@ describe('protoBinaryTest', () => { */ function fillExtensions(msg) { msg.setExtension(proto.jspb.test.extendOptionalInt32, 0); - msg.setExtension(proto.jspb.test.extendOptionalInt64, -0x7fffffff00000000); + msg.setExtension( + proto.jspb.test.extendOptionalInt64, String(-BigInt('0x7fffffff00000000'))); msg.setExtension(proto.jspb.test.extendOptionalUint32, 0x80000000); - msg.setExtension(proto.jspb.test.extendOptionalUint64, 0xf000000000000000); + msg.setExtension( + proto.jspb.test.extendOptionalUint64, String(BigInt('0xf000000000000000'))); msg.setExtension(proto.jspb.test.extendOptionalSint32, -100); - msg.setExtension(proto.jspb.test.extendOptionalSint64, -0x8000000000000000); + msg.setExtension( + proto.jspb.test.extendOptionalSint64, String(-BigInt('0x8000000000000000'))); msg.setExtension(proto.jspb.test.extendOptionalFixed32, 1234); - msg.setExtension(proto.jspb.test.extendOptionalFixed64, 0x1234567800000000); + msg.setExtension( + proto.jspb.test.extendOptionalFixed64, String(BigInt('0x1234567800000000'))); msg.setExtension(proto.jspb.test.extendOptionalSfixed32, -1234); msg.setExtension( - proto.jspb.test.extendOptionalSfixed64, -0x1234567800000000); + proto.jspb.test.extendOptionalSfixed64, + String(-BigInt('0x1234567800000000'))); msg.setExtension(proto.jspb.test.extendOptionalFloat, 1.5); msg.setExtension(proto.jspb.test.extendOptionalDouble, -1.5); msg.setExtension(proto.jspb.test.extendOptionalBool, true); @@ -511,27 +522,32 @@ describe('protoBinaryTest', () => { let submsg = new proto.jspb.test.ExtendsWithMessage(); submsg.setFoo(16); msg.setExtension( - proto.jspb.test.ExtendsWithMessage.optionalExtension, submsg); + proto.jspb.test.ExtendsWithMessage.optionalExtension, submsg); msg.setExtension( - proto.jspb.test.extendOptionalForeignEnum, - proto.jspb.test.ForeignEnum.FOREIGN_FOO); + proto.jspb.test.extendOptionalForeignEnum, + proto.jspb.test.ForeignEnum.FOREIGN_FOO); msg.setExtension(proto.jspb.test.extendRepeatedInt32List, [-42]); - msg.setExtension( - proto.jspb.test.extendRepeatedInt64List, [-0x7fffffff00000000]); + msg.setExtension(proto.jspb.test.extendRepeatedInt64List, [ + String(-BigInt('0x7fffffff00000000')) + ]); msg.setExtension(proto.jspb.test.extendRepeatedUint32List, [0x80000000]); - msg.setExtension( - proto.jspb.test.extendRepeatedUint64List, [0xf000000000000000]); + msg.setExtension(proto.jspb.test.extendRepeatedUint64List, [ + String(BigInt('0xf000000000000000')) + ]); msg.setExtension(proto.jspb.test.extendRepeatedSint32List, [-100]); - msg.setExtension( - proto.jspb.test.extendRepeatedSint64List, [-0x8000000000000000]); + msg.setExtension(proto.jspb.test.extendRepeatedSint64List, [ + String(-BigInt('0x8000000000000000')) + ]); msg.setExtension(proto.jspb.test.extendRepeatedFixed32List, [1234]); - msg.setExtension( - proto.jspb.test.extendRepeatedFixed64List, [0x1234567800000000]); + msg.setExtension(proto.jspb.test.extendRepeatedFixed64List, [ + String(BigInt('0x1234567800000000')) + ]); msg.setExtension(proto.jspb.test.extendRepeatedSfixed32List, [-1234]); - msg.setExtension( - proto.jspb.test.extendRepeatedSfixed64List, [-0x1234567800000000]); + msg.setExtension(proto.jspb.test.extendRepeatedSfixed64List, [ + String(-BigInt('0x1234567800000000')) + ]); msg.setExtension(proto.jspb.test.extendRepeatedFloatList, [1.5]); msg.setExtension(proto.jspb.test.extendRepeatedDoubleList, [-1.5]); msg.setExtension(proto.jspb.test.extendRepeatedBoolList, [true]); @@ -540,35 +556,39 @@ describe('protoBinaryTest', () => { submsg = new proto.jspb.test.ExtendsWithMessage(); submsg.setFoo(1000); msg.setExtension( - proto.jspb.test.ExtendsWithMessage.repeatedExtensionList, [submsg]); + proto.jspb.test.ExtendsWithMessage.repeatedExtensionList, [submsg]); msg.setExtension( - proto.jspb.test.extendRepeatedForeignEnumList, - [proto.jspb.test.ForeignEnum.FOREIGN_FOO]); + proto.jspb.test.extendRepeatedForeignEnumList, + [proto.jspb.test.ForeignEnum.FOREIGN_FOO]); msg.setExtension(proto.jspb.test.extendPackedRepeatedInt32List, [-42]); + msg.setExtension(proto.jspb.test.extendPackedRepeatedInt64List, [ + String(-BigInt('0x7fffffff00000000')) + ]); msg.setExtension( - proto.jspb.test.extendPackedRepeatedInt64List, [-0x7fffffff00000000]); - msg.setExtension( - proto.jspb.test.extendPackedRepeatedUint32List, [0x80000000]); - msg.setExtension( - proto.jspb.test.extendPackedRepeatedUint64List, [0xf000000000000000]); + proto.jspb.test.extendPackedRepeatedUint32List, [0x80000000]); + msg.setExtension(proto.jspb.test.extendPackedRepeatedUint64List, [ + String(BigInt('0xf000000000000000')) + ]); msg.setExtension(proto.jspb.test.extendPackedRepeatedSint32List, [-100]); - msg.setExtension( - proto.jspb.test.extendPackedRepeatedSint64List, [-0x8000000000000000]); + msg.setExtension(proto.jspb.test.extendPackedRepeatedSint64List, [ + String(-BigInt('0x8000000000000000')) + ]); msg.setExtension(proto.jspb.test.extendPackedRepeatedFixed32List, [1234]); - msg.setExtension( - proto.jspb.test.extendPackedRepeatedFixed64List, [0x1234567800000000]); + msg.setExtension(proto.jspb.test.extendPackedRepeatedFixed64List, [ + String(BigInt('0x1234567800000000')) + ]); msg.setExtension(proto.jspb.test.extendPackedRepeatedSfixed32List, [-1234]); - msg.setExtension( - proto.jspb.test.extendPackedRepeatedSfixed64List, - [-0x1234567800000000]); + msg.setExtension(proto.jspb.test.extendPackedRepeatedSfixed64List, [ + String(-BigInt('0x1234567800000000')) + ]); msg.setExtension(proto.jspb.test.extendPackedRepeatedFloatList, [1.5]); msg.setExtension(proto.jspb.test.extendPackedRepeatedDoubleList, [-1.5]); msg.setExtension(proto.jspb.test.extendPackedRepeatedBoolList, [true]); msg.setExtension( - proto.jspb.test.extendPackedRepeatedForeignEnumList, - [proto.jspb.test.ForeignEnum.FOREIGN_FOO]); + proto.jspb.test.extendPackedRepeatedForeignEnumList, + [proto.jspb.test.ForeignEnum.FOREIGN_FOO]); } @@ -589,7 +609,7 @@ describe('protoBinaryTest', () => { it('testUnknownExtension', () => { const msg = new proto.jspb.test.TestExtendable(); fillExtensions(msg); - const writer = new jspb.BinaryWriter(); + const writer = new BinaryWriter(); writer.writeBool((1 << 29) - 1, true); proto.jspb.test.TestExtendable.serializeBinaryToWriter(msg, writer); const encoded = writer.getResultBuffer(); @@ -606,12 +626,13 @@ describe('protoBinaryTest', () => { any.pack(msg.serializeBinary(), 'jspb.test.TestAllTypes'); expect(any.getTypeUrl()) - .toEqual('type.googleapis.com/jspb.test.TestAllTypes'); + .toEqual('type.googleapis.com/jspb.test.TestAllTypes'); const msg2 = any.unpack( - proto.jspb.test.TestAllTypes.deserializeBinary, - 'jspb.test.TestAllTypes'); + proto.jspb.test.TestAllTypes.deserializeBinary, + 'jspb.test.TestAllTypes'); checkAllFields(msg, msg2); }); + }); diff --git a/binary/reader.js b/binary/reader.js old mode 100644 new mode 100755 index 0f8c961..15cbe6d --- a/binary/reader.js +++ b/binary/reader.js @@ -1,33 +1,3 @@ -// Protocol Buffers - Google's data interchange format -// Copyright 2008 Google Inc. All rights reserved. -// https://protobuf.dev/ -// -// 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 This file contains utilities for converting binary, * wire-format protocol buffers into Javascript data structures. @@ -41,16 +11,19 @@ * using the typed jspb code generator, but if you bypass that you'll need * to keep things in sync by hand. * - * @suppress {missingRequire} TODO(b/152540451): this shouldn't be needed * @author aappleby@google.com (Austin Appleby) */ -goog.provide('jspb.BinaryReader'); +goog.module('jspb.binary.reader'); +goog.module.declareLegacyNamespace(); -goog.require('jspb.asserts'); -goog.require('jspb.BinaryConstants'); -goog.require('jspb.BinaryDecoder'); -goog.require('jspb.utils'); +const BinaryConstants = goog.require('jspb.BinaryConstants'); +const asserts = goog.require('goog.asserts'); +const errors = goog.require('jspb.binary.errors'); +const utils = goog.require('jspb.utils'); +const { BinaryDecoder } = goog.require('jspb.binary.decoder'); +const { ByteSource } = goog.require('jspb.binary.bytesource'); +const { ByteString } = goog.requireType('jspb.bytestring'); /** * Whether to enforce that string fields are valid utf8. @@ -67,1365 +40,1510 @@ goog.require('jspb.utils'); const ENFORCE_UTF8 = goog.define('jspb.binary.ENFORCE_UTF8', 'ALWAYS'); // Constrain the set of values to only these two. -jspb.asserts.assert( +asserts.assert( ENFORCE_UTF8 === 'DEPRECATED_PROTO3_ONLY' || ENFORCE_UTF8 === 'ALWAYS'); const /** boolean */ UTF8_PARSING_ERRORS_ARE_FATAL = ENFORCE_UTF8 === 'ALWAYS'; +/** + * Describes options for BinaryReaders. + * + * @record + */ +class BinaryReaderOptions { + constructor() { + /** + * Whether to ignore unknown fields found when parsing. + * + * Normally, if unknown tag numbers are encountered when parsing a message, + * the tag and value are stored in the message instance and then written + * back out when the message is serialized. This allows applications to + * preserve data in messages that have new field definitions which they + * don't yet know about. However, this behavior can have performance + * implications. This property disables this behavior during parsing. + * + * @type {boolean|undefined} + */ + this.discardUnknownFields; + + /** + * When set to `true` bytes fields will be views into the original buffer + * instead of being copies. + * + * This allows teams to reduce copies at the cost of pinning the original + * `ByteSource` in memory. + * + * How this works ultimate depends on how `bytes` fields are parsed. + * + * If `bytes` fields are read as `Uint8Array` via the `readBytes` method (as + * is done by jsproto and immutablejs): `readBytes` will return views onto + * the original buffer as `Uint8Array` objects. Additionally, because + * Uint8Array objects are mutable this may allow unexpected mutations of the + * `ByteSource` or for mutations of the bytesource to affect later read + * operations. If the source is a ByteString, this option is ignored in + * order to preserve the immutability semantics of ByteStrings. + * + * If the `bytes` are read as `ByteString` via the `readByteString` method, + * then this option is only effective if the + * source is also a `ByteString`, otherwise copies need to be made to + * preserve the immutability of the produced `ByteString` objects. + * + * The default is `false` + * @type {boolean|undefined} + */ + this.aliasBytesFields; + + /** + * Whether we should treat newly deserialized data as being immutable. + * + * With this option, we treat newly deserialized data (e.g. from a + * base64-encoded string) as being immutable, so that it can be safely + * aliased in a ByteString. If you set this option, you then cannot alias + * into a Uint8Array, as its underlying ArrayBuffer could be unwrapped. + * + * @type {boolean|undefined} + */ + this.treatNewDataAsImmutable; + } +} +/* + * Handling Errors + * + * There are two classes of errors that should be considered below. + * + * Simple consistency checks: use `goog.asserts` + * + * These are cases related to function invariants. For example, in order to + * call `readString` the current field must have WireType.DELIMITED. Since we + * also control all the callsites it is reasonable to rely on our own tests to + * catch mistakes. + * + * Data based conditions: use `jspb.binary.invalid_encoding_errors` + * + * These are conditions where the _data_ is wrong in some way. For example, a + * varint is overlong, a delimited field overflows/underflows. These cases + * cannot be eliminated by better testing since they depend on the structure of + * data supplied to the parsing routines, so `goog.asserts` are not a good + * approach + */ /** * BinaryReader implements the decoders for all the wire types specified in - * https://protobuf.dev/programming-guides/encoding/. + * https://developers.google.com/protocol-buffers/docs/encoding. * - * @param {jspb.ByteSource=} opt_bytes The bytes we're reading from. - * @param {number=} opt_start The optional offset to start reading at. - * @param {number=} opt_length The optional length of the block to read - - * we'll throw an assertion if we go off the end of the block. - * @constructor * @struct - * @export + * @final */ -jspb.BinaryReader = function(opt_bytes, opt_start, opt_length) { +class BinaryReader { /** - * Wire-format decoder. - * @private {!jspb.BinaryDecoder} + * @param {?ByteSource|!ByteString=} bytes The bytes we're reading from. + * @param {number=} start The optional offset to start reading at. + * @param {number=} length The optional length of the block to read - + * we'll throw an assertion if we go off the end of the block. + * @param {!BinaryReaderOptions=} options Options for this BinaryReader. */ - this.decoder_ = jspb.BinaryDecoder.alloc(opt_bytes, opt_start, opt_length); + constructor(bytes, start, length, options) { + /** + * Current options for this reader + * @private {boolean} + */ + this.discardUnknownFields; + /** + * Wire-format decoder. + * @const {!BinaryDecoder} + */ + this.decoder_ = BinaryDecoder.alloc(bytes, start, length, options); + + /** + * Cursor immediately before the field tag. + * @private {number} + */ + this.fieldCursor_ = this.decoder_.getCursor(); + + /** + * Field number of the next field in the buffer, filled in by nextField(). + * @private {number} + */ + this.nextField_ = BinaryConstants.INVALID_FIELD_NUMBER; + + /** + * The combined wire-type and field number of the next field in the buffer, + * filled in by nextField(). + * @private {number} + */ + this.nextTag_ = BinaryConstants.INVALID_TAG; + + /** + * Wire type of the next proto field in the buffer, filled in by + * nextField(). + * @private {!BinaryConstants.WireType} + */ + this.nextWireType_ = BinaryConstants.WireType.INVALID; + this.setOptions(options); + } /** - * Cursor immediately before the field tag. - * @private {number} + * @param {!BinaryReaderOptions=} options options for this decoder. + * @private */ - this.fieldCursor_ = this.decoder_.getCursor(); + setOptions({ discardUnknownFields = false } = {}) { + this.discardUnknownFields = discardUnknownFields; + } + /** - * Field number of the next field in the buffer, filled in by nextField(). - * @private {number} + * Pops an instance off the instance cache, or creates one if the cache is + * empty. + * @param {?ByteSource|!ByteString=} bytes The bytes we're reading from. + * @param {number=} start The optional offset to start reading at. + * @param {number=} length The optional length of the block to read - + * we'll throw an assertion if we go off the end of the block. + * @param {!BinaryReaderOptions=} options + * @return {!BinaryReader} + * @suppress {visibility} accesses private properties of decoder */ - this.nextField_ = jspb.BinaryConstants.INVALID_FIELD_NUMBER; + static alloc(bytes, start, length, options) { + if (BinaryReader.instanceCache_.length) { + const newReader = BinaryReader.instanceCache_.pop(); + newReader.setOptions(options); + newReader.decoder_.init(bytes, start, length, options); + return newReader; + } else { + return new BinaryReader(bytes, start, length, options); + } + } + + /** - * Wire type of the next proto field in the buffer, filled in by - * nextField(). - * @private {jspb.BinaryConstants.WireType} + * Puts this instance back in the instance cache. */ - this.nextWireType_ = jspb.BinaryConstants.WireType.INVALID; + free() { + this.decoder_.clear(); + this.nextTag_ = BinaryConstants.INVALID_TAG; + this.nextField_ = BinaryConstants.INVALID_FIELD_NUMBER; + this.nextWireType_ = BinaryConstants.WireType.INVALID; + + if (BinaryReader.instanceCache_.length < 100) { + BinaryReader.instanceCache_.push(this); + } + } + /** - * Set to true if this reader encountered an error due to corrupt data. - * @private {boolean} + * Returns the cursor immediately before the current field's tag. + * @return {number} The internal read cursor. */ - this.error_ = false; + getFieldCursor() { + return this.fieldCursor_; + } + /** - * User-defined reader callbacks. - * @private {?Object} + * Returns the internal read cursor. + * @return {number} The internal read cursor. */ - this.readCallbacks_ = null; -}; + getCursor() { + return this.decoder_.getCursor(); + } + /** @return {boolean} */ + dataIsImmutable() { + return this.decoder_.dataIsImmutable(); + } -/** - * Global pool of BinaryReader instances. - * @private {!Array} - */ -jspb.BinaryReader.instanceCache_ = []; + /** + * Returns the raw buffer. + * + * Throws if the internal buffer is immutable. + * + * @return {?Uint8Array} The raw buffer. + */ + getBuffer() { + return this.decoder_.getBuffer(); + } + /** + * Returns the raw buffer as a byte string. + * + * Throws if the internal buffer is mutable. + * + * @return {?ByteString} The raw buffer. + */ + getBufferAsByteString() { + return this.decoder_.getBufferAsByteString(); + } -/** - * @export - */ -jspb.BinaryReader.clearInstanceCache = function() { - jspb.BinaryReader.instanceCache_ = []; -} + /** + * @return {number} The combined wire type and field number of the next field + * in the buffer, or INVALID_TAG if there is no next field. This is an + * unsigned 32-bit integer value with the lower three bits the wire type + * and the upper 29 bits the field number. + */ + getTag() { + return this.nextTag_; + } -/** - @return {number} - * @export - */ -jspb.BinaryReader.getInstanceCacheLength = function() { - return jspb.BinaryReader.instanceCache_.length; -} + /** + * @return {number} The field number of the next field in the buffer, or + * INVALID_FIELD_NUMBER if there is no next field. + */ + getFieldNumber() { + return this.nextField_; + } -/** - * Pops an instance off the instance cache, or creates one if the cache is - * empty. - * @param {jspb.ByteSource=} opt_bytes The bytes we're reading from. - * @param {number=} opt_start The optional offset to start reading at. - * @param {number=} opt_length The optional length of the block to read - - * we'll throw an assertion if we go off the end of the block. - * @return {!jspb.BinaryReader} - * @export - */ -jspb.BinaryReader.alloc = function(opt_bytes, opt_start, opt_length) { - if (jspb.BinaryReader.instanceCache_.length) { - var newReader = jspb.BinaryReader.instanceCache_.pop(); - if (opt_bytes) { - newReader.decoder_.setBlock(opt_bytes, opt_start, opt_length); - } - return newReader; - } else { - return new jspb.BinaryReader(opt_bytes, opt_start, opt_length); + /** + * @return {!BinaryConstants.WireType} The wire type of the next field + * in the stream, or WireType.INVALID if there is no next field. + */ + getWireType() { + return this.nextWireType_; } -}; -/** - * Alias for the above method. - * @param {jspb.ByteSource=} opt_bytes The bytes we're reading from. - * @param {number=} opt_start The optional offset to start reading at. - * @param {number=} opt_length The optional length of the block to read - - * we'll throw an assertion if we go off the end of the block. - * @return {!jspb.BinaryReader} - * @export - */ -jspb.BinaryReader.prototype.alloc = jspb.BinaryReader.alloc; + /** + * @return {boolean} Whether the current wire type is an end-group tag. Used + * as + * an exit condition in decoder loops in generated code. + */ + isEndGroup() { + return this.nextWireType_ == BinaryConstants.WireType.END_GROUP; + } + /** + * @return {boolean} Whether the current wire type is a delimited field. Used + * to + * conditionally parse packed repeated fields. + */ + isDelimited() { + return this.nextWireType_ == BinaryConstants.WireType.DELIMITED; + } -/** - * Puts this instance back in the instance cache. - * @export - */ -jspb.BinaryReader.prototype.free = function() { - this.decoder_.clear(); - this.nextField_ = jspb.BinaryConstants.INVALID_FIELD_NUMBER; - this.nextWireType_ = jspb.BinaryConstants.WireType.INVALID; - this.error_ = false; - this.readCallbacks_ = null; - if (jspb.BinaryReader.instanceCache_.length < 100) { - jspb.BinaryReader.instanceCache_.push(this); + /** + * Rewinds the stream cursor to the beginning of the buffer and resets all + * internal state. + * @package + */ + reset() { + this.decoder_.reset(); + this.fieldCursor_ = this.decoder_.getCursor(); + this.nextTag_ = BinaryConstants.INVALID_TAG; + this.nextField_ = BinaryConstants.INVALID_FIELD_NUMBER; + this.nextWireType_ = BinaryConstants.WireType.INVALID; } -}; -/** - * Returns the cursor immediately before the current field's tag. - * @return {number} The internal read cursor. - * @export - */ -jspb.BinaryReader.prototype.getFieldCursor = function() { - return this.fieldCursor_; -}; + /** + * Advances the stream cursor by the given number of bytes. + * @param {number} count The number of bytes to advance by. + */ + advance(count) { + this.decoder_.advance(count); + } -/** - * Returns the internal read cursor. - * @return {number} The internal read cursor. - * @export - */ -jspb.BinaryReader.prototype.getCursor = function() { - return this.decoder_.getCursor(); -}; + /** + * Reads the next field header in the stream if there is one, returns true if + * we saw a valid field header or false if we've read the whole stream. + * Throws an error if we encountered a deprecated START_GROUP/END_GROUP field. + * @return {boolean} True if the stream contains more fields. + */ + nextField() { + // If we're at the end of the block, there are no more fields. + if (this.decoder_.atEnd()) { + return false; + } + this.assertPriorFieldWasRead(); + // No need to check cursor position here, as that is readUnsignedVarint32s + // responsibility. + // Otherwise just read the header of the next field. + this.fieldCursor_ = this.decoder_.getCursor(); + const header = BinaryDecoder.readUnsignedVarint32(this.decoder_); -/** - * Returns the raw buffer. - * @return {?Uint8Array} The raw buffer. - * @export - */ -jspb.BinaryReader.prototype.getBuffer = function() { - return this.decoder_.getBuffer(); -}; + const nextField = parseFieldNumber(header); + const nextWireType = parseWireType(header); + // If the wire type isn't one of the valid ones, something's broken. + if (!BinaryConstants.isValidWireType(nextWireType)) { + throw errors.invalidWireTypeError(nextWireType, this.fieldCursor_); + } -/** - * @return {number} The field number of the next field in the buffer, or - * INVALID_FIELD_NUMBER if there is no next field. - * @export - */ -jspb.BinaryReader.prototype.getFieldNumber = function() { - return this.nextField_; -}; + // Zero is not a valid field number and we should never see a negative as + // we've already shifted right unsigned. + if (nextField < 1) { + throw errors.invalidFieldNumberError(nextField, this.fieldCursor_); + } + this.nextTag_ = header; + this.nextField_ = nextField; + this.nextWireType_ = nextWireType; -/** - * @return {jspb.BinaryConstants.WireType} The wire type of the next field - * in the stream, or WireType.INVALID if there is no next field. - * @export - */ -jspb.BinaryReader.prototype.getWireType = function() { - return this.nextWireType_; -}; + return true; + } + /** + * If the stream contains a following field with the specified tag (field + * number and wire type), reads its field header and makes the reader ready to + * read its value. If the stream does not contain another field, or its tag is + * different, does not change the state of the stream. + * + * TIP: methods like `readMessage` may destroy the reader state for the tag, + * so call `getTag()` outside a loop, before calling reader methods to pass + * into this method. + * + * @param {number} tag The field tag to look for. + * @return {boolean} Whether another instance of the current tag is read. + */ + nextFieldIfTagEqualTo(tag) { + this.assertPriorFieldWasRead(); + asserts.assert( + BinaryConstants.isValidWireType(parseWireType(tag)) && + parseFieldNumber(tag) > 0, + 'Must pass a valid tag.'); + + const fieldCursorOrNegative = + this.decoder_.readUnsignedVarint32IfEqualTo(tag); + const matched = fieldCursorOrNegative >= 0; + if (matched) { + this.fieldCursor_ = fieldCursorOrNegative; + + this.nextTag_ = tag; + this.nextField_ = parseFieldNumber(tag); + this.nextWireType_ = parseWireType(tag); + } + return matched; + } -/** - * @return {boolean} Whether the current wire type is a delimited field. Used to - * conditionally parse packed repeated fields. - * @export - */ -jspb.BinaryReader.prototype.isDelimited = function() { - return this.nextWireType_ == jspb.BinaryConstants.WireType.DELIMITED; -}; + /** + * Helper to ensure that the prior field was correctly read by advancing the + * cursor. + * @private + */ + assertPriorFieldWasRead() { + if (asserts.ENABLE_ASSERTS && + this.nextTag_ !== BinaryConstants.INVALID_TAG) { + // If we aren't at the first field, make sure that the previous caller of + // nextField actually read the field (by calling an appropriate `read*` or + // `skip*` method). + // To do this we go back and redo the work to move cursor to where it was + // at the end of the last nextField() call and just ensure that we have + // advanced beyond that + const currentCursor = this.decoder_.getCursor(); + this.decoder_.setCursor(this.fieldCursor_); + BinaryDecoder.readUnsignedVarint32(this.decoder_); + if (this.nextWireType_ === BinaryConstants.WireType.END_GROUP || + this.nextWireType_ === BinaryConstants.WireType.START_GROUP) { + // This case should be impossible, as every read* skip* method would + // likely throw. Direct mutation of the cursor could do it. + asserts.assert( + currentCursor === this.decoder_.getCursor(), + 'Expected to not advance the cursor. Group tags do not have values.'); + } else { + asserts.assert( + currentCursor > this.decoder_.getCursor(), + 'Expected to read the field, did you forget to call a read or skip method?'); + } + this.decoder_.setCursor(currentCursor); + } + } -/** - * @return {boolean} Whether the current wire type is an end-group tag. Used as - * an exit condition in decoder loops in generated code. - * @export - */ -jspb.BinaryReader.prototype.isEndGroup = function() { - return this.nextWireType_ == jspb.BinaryConstants.WireType.END_GROUP; -}; + /** + * Skips over the next varint field in the binary stream. + */ + skipVarintField() { + // This case should be impossible but jsproto calls this method without + // first checking wiretype + if (this.nextWireType_ != BinaryConstants.WireType.VARINT) { + asserts.fail('Invalid wire type for skipVarintField'); + this.skipField(); + return; + } + this.decoder_.skipVarint(); + } -/** - * Returns true if this reader hit an error due to corrupt data. - * @return {boolean} - * @export - */ -jspb.BinaryReader.prototype.getError = function() { - return this.error_ || this.decoder_.getError(); -}; + /** + * Skips over the next delimited field in the binary stream. + * @return {number} The length of the delimited field payload, not including + * the field header. + */ + skipDelimitedField() { + // This case should be impossible but jsproto calls this method without + // first checking wiretype + if (this.nextWireType_ != BinaryConstants.WireType.DELIMITED) { + asserts.fail('Invalid wire type for skipDelimitedField'); + this.skipField(); + return 0; + } -/** - * Points this reader at a new block of bytes. - * @param {!Uint8Array} bytes The block of bytes we're reading from. - * @param {number} start The offset to start reading at. - * @param {number} length The length of the block to read. - * @export - */ -jspb.BinaryReader.prototype.setBlock = function(bytes, start, length) { - this.decoder_.setBlock(bytes, start, length); - this.nextField_ = jspb.BinaryConstants.INVALID_FIELD_NUMBER; - this.nextWireType_ = jspb.BinaryConstants.WireType.INVALID; -}; + const length = BinaryDecoder.readUnsignedVarint32(this.decoder_); + this.decoder_.advance(length); + return length; + } -/** - * Rewinds the stream cursor to the beginning of the buffer and resets all - * internal state. - * @export - */ -jspb.BinaryReader.prototype.reset = function() { - this.decoder_.reset(); - this.nextField_ = jspb.BinaryConstants.INVALID_FIELD_NUMBER; - this.nextWireType_ = jspb.BinaryConstants.WireType.INVALID; -}; + /** + * Skips over the next fixed32 field in the binary stream. + * @private + */ + skipFixed32Field() { + asserts.assert(this.nextWireType_ === BinaryConstants.WireType.FIXED32); + this.decoder_.advance(4); + } -/** - * Advances the stream cursor by the given number of bytes. - * @param {number} count The number of bytes to advance by. - * @export - */ -jspb.BinaryReader.prototype.advance = function(count) { - this.decoder_.advance(count); -}; + /** + * Skips over the next fixed64 field in the binary stream. + * @private + */ + skipFixed64Field() { + asserts.assert(this.nextWireType_ === BinaryConstants.WireType.FIXED64); + this.decoder_.advance(8); + } -/** - * Reads the next field header in the stream if there is one, returns true if - * we saw a valid field header or false if we've read the whole stream. - * Throws an error if we encountered a deprecated START_GROUP/END_GROUP field. - * @return {boolean} True if the stream contains more fields. - * @export - */ -jspb.BinaryReader.prototype.nextField = function() { - // If we're at the end of the block, there are no more fields. - if (this.decoder_.atEnd()) { - return false; + /** + * Skips over the next group field in the binary stream. + * @private + */ + skipGroup() { + const previousField = this.nextField_; + do { + if (!this.nextField()) { + throw errors.unmatchedStartGroupEofError(); + } + if (this.nextWireType_ == BinaryConstants.WireType.END_GROUP) { + // Group end: check that it matches top-of-stack. + if (this.nextField_ != previousField) { + throw errors.unmatchedStartGroupError(); + } + return; + } + this.skipField(); + } while (true); } - // If we hit an error decoding the previous field, stop now before we - // try to decode anything else - if (this.getError()) { - jspb.asserts.fail('Decoder hit an error'); - return false; + + /** + * Skips over the next field in the binary stream - this is useful if we're + * decoding a message that contain unknown fields. + */ + skipField() { + switch (this.nextWireType_) { + case BinaryConstants.WireType.VARINT: + this.skipVarintField(); + break; + case BinaryConstants.WireType.FIXED64: + this.skipFixed64Field(); + break; + case BinaryConstants.WireType.DELIMITED: + this.skipDelimitedField(); + break; + case BinaryConstants.WireType.FIXED32: + this.skipFixed32Field(); + break; + case BinaryConstants.WireType.START_GROUP: + this.skipGroup(); + break; + default: + throw errors.invalidWireTypeError( + this.nextWireType_, this.fieldCursor_); + } } - // Otherwise just read the header of the next field. - this.fieldCursor_ = this.decoder_.getCursor(); - var header = this.decoder_.readUnsignedVarint32(); + /** + * Skips over the entire content to the end of the stream. + */ + skipToEnd() { + this.decoder_.setCursor(this.decoder_.getEnd()); + } - var nextField = header >>> 3; - var nextWireType = /** @type {jspb.BinaryConstants.WireType} */ - (header & 0x7); + /** + * Reads a single field as an uninterpreted bytestring. + * @return {!ByteString|undefined} + */ + readUnknownField() { + // read the field cursor prior to calling skipField, otherwise skipping + // a group will reset the field cursor. + const begin = this.getFieldCursor(); + this.skipField(); + return this.readUnknownFieldsStartingFrom(begin); + } - // If the wire type isn't one of the valid ones, something's broken. - if (nextWireType != jspb.BinaryConstants.WireType.VARINT && - nextWireType != jspb.BinaryConstants.WireType.FIXED32 && - nextWireType != jspb.BinaryConstants.WireType.FIXED64 && - nextWireType != jspb.BinaryConstants.WireType.DELIMITED && - nextWireType != jspb.BinaryConstants.WireType.START_GROUP && - nextWireType != jspb.BinaryConstants.WireType.END_GROUP) { - jspb.asserts.fail( - 'Invalid wire type: %s (at position %s)', nextWireType, - this.fieldCursor_); - this.error_ = true; - return false; + /** + * Reads a range of unknown field(s) as a single bytestring. + * @param {number} fieldOffset + * @return {!ByteString|undefined} + */ + readUnknownFieldsStartingFrom(fieldOffset) { + if (!this.discardUnknownFields) { + // It is important that this is actually an immutable reference since the + // unknownFieldset takes 'ownership' over the data. So we read as a + // ByteString. + const currentOffset = this.decoder_.getCursor(); + const fieldLength = currentOffset - fieldOffset; + this.decoder_.setCursor(fieldOffset); + const unknownField = this.decoder_.readByteString(fieldLength); + // double check our own math. + asserts.assert(currentOffset == this.decoder_.getCursor()); + // lazily instantiate the unknown fields structure. + return unknownField; + } + return undefined; } - this.nextField_ = nextField; - this.nextWireType_ = nextWireType; + /** + * Reads a field of any valid non-message type from the binary stream. + * + * Returns `null` if the type could not be parsed because the data on the wire + * doesn't match the `fieldType`. + * @param {!BinaryConstants.FieldType} fieldType + * @return {boolean|number|string|!Uint8Array|null} + */ + readAny(fieldType) { + // If the wire types don't match just return null, it means that there is an + // invalid value on the wire. In normal apps/jspb we handle this as an + // 'unknown field' but in this API we just skip over it as we have no place + // to store unknowns + if (BinaryConstants.FieldTypeToWireType(fieldType) !== this.nextWireType_) { + return null; + } + const fieldTypes = BinaryConstants.FieldType; + switch (fieldType) { + case fieldTypes.DOUBLE: + return this.readDouble(); + case fieldTypes.FLOAT: + return this.readFloat(); + case fieldTypes.INT64: + return this.readInt64(); + case fieldTypes.UINT64: + return this.readUint64(); + case fieldTypes.INT32: + return this.readInt32(); + case fieldTypes.FIXED64: + return this.readFixed64(); + case fieldTypes.FIXED32: + return this.readFixed32(); + case fieldTypes.BOOL: + return this.readBool(); + case fieldTypes.STRING: + return this.readString(); + case fieldTypes.GROUP: + asserts.fail('Group field type not supported in readAny()'); + case fieldTypes.MESSAGE: + asserts.fail('Message field type not supported in readAny()'); + case fieldTypes.BYTES: + return this.readBytes(); + case fieldTypes.UINT32: + return this.readUint32(); + case fieldTypes.ENUM: + return this.readEnum(); + case fieldTypes.SFIXED32: + return this.readSfixed32(); + case fieldTypes.SFIXED64: + return this.readSfixed64(); + case fieldTypes.SINT32: + return this.readSint32(); + case fieldTypes.SINT64: + return this.readSint64(); + default: + asserts.fail('Invalid field type in readAny()'); + } + return null; + } - return true; -}; + /** + * Deserialize a proto into the provided message object using the provided + * reader function. This function is templated as we currently have one client + * who is using manual deserialization instead of the code-generated versions. + * @template T,A,B,C + * @param {T} message + * @param {function(T, !BinaryReader, A, B, C)} reader + * @param {A=} contextA + * @param {B=} contextB + * @param {C=} contextC + * @return {T} + */ + readMessage(message, reader, contextA, contextB, contextC) { + asserts.assert(this.nextWireType_ == BinaryConstants.WireType.DELIMITED); + + // Save the current endpoint of the decoder and move it to the end of the + // embedded message. + const oldEnd = this.decoder_.getEnd(); + const length = BinaryDecoder.readUnsignedVarint32(this.decoder_); + const newEnd = this.decoder_.getCursor() + length; + let underflowLength = newEnd - oldEnd; + if (underflowLength <= 0) { + this.decoder_.setEnd(newEnd); + + // Deserialize the embedded message. + reader(message, this, contextA, contextB, contextC); + + underflowLength = newEnd - this.decoder_.getCursor(); + } + if (underflowLength) { + throw errors.messageLengthMismatchError(length, length - underflowLength); + } + // Advance the decoder past the embedded message and restore the endpoint. + this.decoder_.setCursor(newEnd); + this.decoder_.setEnd(oldEnd); + return message; + } -/** - * Winds the reader back to just before this field's header. - * @export - */ -jspb.BinaryReader.prototype.unskipHeader = function() { - this.decoder_.unskipVarint((this.nextField_ << 3) | this.nextWireType_); -}; + /** + * Deserialize a proto into the provided message object using the provided + * reader function, assuming that the message is serialized as a group + * with the given tag. + * @template T + * @param {number} field + * @param {T} message + * @param {function(T, !BinaryReader)} reader + * @return {T} + */ + readGroup(field, message, reader) { + // Ensure that the wire type is correct. + asserts.assert(this.nextWireType_ == BinaryConstants.WireType.START_GROUP); + // Ensure that the field number is correct. + asserts.assert(this.nextField_ == field); + + // Deserialize the message. The deserialization will stop at an END_GROUP + // tag. + reader(message, this); + + if (this.nextWireType_ !== BinaryConstants.WireType.END_GROUP) { + // This case should be impossible assuming we trust all the reader + // callbacks. In other words this really means the `reader` did the + // wrong thing. + throw errors.groupDidNotEndWithEndGroupError(); + } else if (this.nextField_ !== field) { + // this can happen if start and end group tags are nested improperly + throw errors.unmatchedStartGroupError(); + } + return message; + } + /** + * Whether the current field could be a valid start of a message set group. + * @return {boolean} + */ + isMessageSetGroup() { + return this.getTag() === MESSAGE_SET_START_GROUP_TAG; + } -/** - * Skips all contiguous fields whose header matches the one we just read. - * @export - */ -jspb.BinaryReader.prototype.skipMatchingFields = function() { - var field = this.nextField_; - this.unskipHeader(); + /** + * Deserialize a message-set wire-format group, calling the provided callaback + * with the type ID and reader to read the message with. + * + * See go/messageset-wire-format + * @param {function(number, !BinaryReader): void} readerCallback Function + * called with the type ID and a reader for the message content. + */ + readMessageSetGroup(readerCallback) { + asserts.assert(this.isMessageSetGroup()); + + // A message set group is encodes like: + // repeated group Item = 1 { + // required uint32 typeId = 2; + // required bytes message = 3; + // } + // + let typeId = 0; + // The offset to the message payload, or -1 if consumed. + let messageCursor = 0; + while (this.nextField() && !this.isEndGroup()) { + // See go/malformed-message-set-parsing If malformed messages repeat the + // typeId or message fields, only use the first value of either within + // the group. + if (this.getTag() === MESSAGE_SET_TYPE_ID_TAG && !typeId) { + typeId = this.readUint32(); + if (messageCursor) { + asserts.assert(messageCursor > 0); + // Backup the parsing to the message payload. + // No need to restore the position, we'll simply reread the type id + // again, but skip doing anything because its already been read once. + + // Reset these. Because we are going to modify the cursor we need to + // skip the consistency checks in nextField() and pretend like this is + // a fresh BinaryReader instance. + if (asserts.ENABLE_ASSERTS) { + this.nextTag_ = BinaryConstants.INVALID_TAG; + this.nextWireType_ = BinaryConstants.WireType.INVALID; + } + + // Reset our cursor to where the message was, and pretend like we had + // not seen the message payload yet. + this.decoder_.setCursor(messageCursor); + messageCursor = 0; + } + } else if (this.getTag() === MESSAGE_SET_MESSAGE_TAG && !messageCursor) { + if (typeId) { + messageCursor = -1; + this.readMessage(typeId, readerCallback); + } else { + // Save the cursor to read the message + // after we have the type ID. + messageCursor = this.getFieldCursor(); + this.skipDelimitedField(); + } + } else { + // Either we have already read the payload or this is not a valid + // messageset member. As a practical matter we don't have a place to + // store this unknown field and simply skipping is consistent with the + // Java and C++ impls + this.skipField(); + } + } - while (this.nextField() && (this.getFieldNumber() == field)) { - this.skipField(); + // If we do not have an end tag, if we did not have a message, or if we + // did not have a field number, drop out. + if (this.getTag() !== MESSAGE_SET_END_TAG || !messageCursor || !typeId) { + throw errors.malformedBinaryBytesForMessageSet(); + } } - if (!this.decoder_.atEnd()) { - this.unskipHeader(); + /** + * Reads a signed 32-bit integer field from the binary stream, or throws an + * error if the next field in the stream is not of the correct wire type. + * + * @return {number} The value of the signed 32-bit integer field. + */ + readInt32() { + asserts.assert(this.nextWireType_ == BinaryConstants.WireType.VARINT); + return BinaryDecoder.readSignedVarint32(this.decoder_); } -}; -/** - * Skips over the next varint field in the binary stream. - * @export - */ -jspb.BinaryReader.prototype.skipVarintField = function() { - if (this.nextWireType_ != jspb.BinaryConstants.WireType.VARINT) { - jspb.asserts.fail('Invalid wire type for skipVarintField'); - this.skipField(); - return; + /** + * Reads a signed 64-bit integer field from the binary stream, or throws an + * error if the next field in the stream is not of the correct wire type. + * + * @return {number} The value of the signed 64-bit integer field. + */ + readInt64() { + asserts.assert(this.nextWireType_ == BinaryConstants.WireType.VARINT); + return BinaryDecoder.readSignedVarint64(this.decoder_); } - this.decoder_.skipVarint(); -}; + + /** + * Reads a signed 64-bit integer field from the binary stream, or throws an + * error if the next field in the stream is not of the correct wire type. + * + * Returns the value as a string. + * + * @return {string} The value of the signed 64-bit integer field as a decimal + * string. + */ + readInt64String() { + asserts.assert(this.nextWireType_ == BinaryConstants.WireType.VARINT); + return BinaryDecoder.readSignedVarint64String(this.decoder_); + } -/** - * Skips over the next delimited field in the binary stream. - * @export - */ -jspb.BinaryReader.prototype.skipDelimitedField = function() { - if (this.nextWireType_ != jspb.BinaryConstants.WireType.DELIMITED) { - jspb.asserts.fail('Invalid wire type for skipDelimitedField'); - this.skipField(); - return; + /** + * Reads an unsigned 32-bit integer field from the binary stream, or throws an + * error if the next field in the stream is not of the correct wire type. + * + * @return {number} The value of the unsigned 32-bit integer field. + */ + readUint32() { + asserts.assert(this.nextWireType_ == BinaryConstants.WireType.VARINT); + return BinaryDecoder.readUnsignedVarint32(this.decoder_); } - var length = this.decoder_.readUnsignedVarint32(); - this.decoder_.advance(length); -}; + /** + * Reads an unsigned 64-bit integer field from the binary stream, or throws an + * error if the next field in the stream is not of the correct wire type. + * + * @return {number} The value of the unsigned 64-bit integer field. + */ + readUint64() { + asserts.assert(this.nextWireType_ == BinaryConstants.WireType.VARINT); + return BinaryDecoder.readUnsignedVarint64(this.decoder_); + } -/** - * Skips over the next fixed32 field in the binary stream. - * @export - */ -jspb.BinaryReader.prototype.skipFixed32Field = function() { - if (this.nextWireType_ != jspb.BinaryConstants.WireType.FIXED32) { - jspb.asserts.fail('Invalid wire type for skipFixed32Field'); - this.skipField(); - return; + /** + * Reads an unsigned 64-bit integer field from the binary stream, or throws an + * error if the next field in the stream is not of the correct wire type. + * + * Returns the value as a string. + * + * @return {string} The value of the unsigned 64-bit integer field as a + * decimal string. + */ + readUint64String() { + asserts.assert(this.nextWireType_ == BinaryConstants.WireType.VARINT); + return BinaryDecoder.readUnsignedVarint64String(this.decoder_); } - this.decoder_.advance(4); -}; + /** + * Reads a signed zigzag-encoded 32-bit integer field from the binary stream, + * or throws an error if the next field in the stream is not of the correct + * wire type. + * + * @return {number} The value of the signed 32-bit integer field. + */ + readSint32() { + asserts.assert(this.nextWireType_ == BinaryConstants.WireType.VARINT); + return BinaryDecoder.readZigzagVarint32(this.decoder_); + } -/** - * Skips over the next fixed64 field in the binary stream. - * @export - */ -jspb.BinaryReader.prototype.skipFixed64Field = function() { - if (this.nextWireType_ != jspb.BinaryConstants.WireType.FIXED64) { - jspb.asserts.fail('Invalid wire type for skipFixed64Field'); - this.skipField(); - return; + /** + * Reads a signed zigzag-encoded 64-bit integer field from the binary stream, + * or throws an error if the next field in the stream is not of the correct + * wire type. + * + * @return {number} The value of the signed 64-bit integer field. + */ + readSint64() { + asserts.assert(this.nextWireType_ == BinaryConstants.WireType.VARINT); + return BinaryDecoder.readZigzagVarint64(this.decoder_); } - this.decoder_.advance(8); -}; + /** + * Reads a signed zigzag-encoded 64-bit integer field from the binary stream, + * or throws an error if the next field in the stream is not of the correct + * wire type. + * + * @return {string} The value of the signed 64-bit integer field as a decimal + * string. + */ + readSint64String() { + asserts.assert(this.nextWireType_ == BinaryConstants.WireType.VARINT); + return BinaryDecoder.readZigzagVarint64String(this.decoder_); + } -/** - * Skips over the next group field in the binary stream. - * @export - */ -jspb.BinaryReader.prototype.skipGroup = function() { - var previousField = this.nextField_; - do { - if (!this.nextField()) { - jspb.asserts.fail('Unmatched start-group tag: stream EOF'); - this.error_ = true; - return; - } - if (this.nextWireType_ == jspb.BinaryConstants.WireType.END_GROUP) { - // Group end: check that it matches top-of-stack. - if (this.nextField_ != previousField) { - jspb.asserts.fail('Unmatched end-group tag'); - this.error_ = true; - return; - } - return; - } - this.skipField(); - } while (true); -}; + /** + * Reads an unsigned 32-bit fixed-length integer fiield from the binary + * stream, or throws an error if the next field in the stream is not of the + * correct wire type. + * + * @return {number} The value of the double field. + */ + readFixed32() { + asserts.assert(this.nextWireType_ == BinaryConstants.WireType.FIXED32); + return BinaryDecoder.readUint32(this.decoder_); + } -/** - * Skips over the next field in the binary stream - this is useful if we're - * decoding a message that contain unknown fields. - * @export - */ -jspb.BinaryReader.prototype.skipField = function() { - switch (this.nextWireType_) { - case jspb.BinaryConstants.WireType.VARINT: - this.skipVarintField(); - break; - case jspb.BinaryConstants.WireType.FIXED64: - this.skipFixed64Field(); - break; - case jspb.BinaryConstants.WireType.DELIMITED: - this.skipDelimitedField(); - break; - case jspb.BinaryConstants.WireType.FIXED32: - this.skipFixed32Field(); - break; - case jspb.BinaryConstants.WireType.START_GROUP: - this.skipGroup(); - break; - default: - jspb.asserts.fail('Invalid wire encoding for field.'); + /** + * Reads an unsigned 64-bit fixed-length integer fiield from the binary + * stream, or throws an error if the next field in the stream is not of the + * correct wire type. + * + * @return {number} The value of the float field. + */ + readFixed64() { + asserts.assert(this.nextWireType_ == BinaryConstants.WireType.FIXED64); + return BinaryDecoder.readUint64(this.decoder_); } -}; -/** - * Registers a user-defined read callback. - * @param {string} callbackName - * @param {function(!jspb.BinaryReader):*} callback - * @export - */ -jspb.BinaryReader.prototype.registerReadCallback = function( - callbackName, callback) { - if (this.readCallbacks_ === null) { - this.readCallbacks_ = {}; + /** + * Reads an unsigned 64-bit integer field from the binary stream as a string, + * or throws an error if the next field in the stream is not of the correct + * wire type. + * + * Returns the value as a string. + * + * @return {string} The value of the unsigned 64-bit integer field as a + * decimal string. + */ + readFixed64String() { + asserts.assert(this.nextWireType_ == BinaryConstants.WireType.FIXED64); + return BinaryDecoder.readUint64String(this.decoder_); } - jspb.asserts.assert(!this.readCallbacks_[callbackName]); - this.readCallbacks_[callbackName] = callback; -}; + /** + * Reads a signed 32-bit fixed-length integer fiield from the binary stream, + * or throws an error if the next field in the stream is not of the correct + * wire type. + * + * @return {number} The value of the signed 32-bit integer field. + */ + readSfixed32() { + asserts.assert(this.nextWireType_ == BinaryConstants.WireType.FIXED32); + return BinaryDecoder.readInt32(this.decoder_); + } -/** - * Runs a registered read callback. - * @param {string} callbackName The name the callback is registered under. - * @return {*} The value returned by the callback. - * @export - */ -jspb.BinaryReader.prototype.runReadCallback = function(callbackName) { - jspb.asserts.assert(this.readCallbacks_ !== null); - var callback = this.readCallbacks_[callbackName]; - jspb.asserts.assert(callback); - return callback(this); -}; - -/** - * Reads a field of any valid non-message type from the binary stream. - * @param {jspb.BinaryConstants.FieldType} fieldType - * @return {jspb.AnyFieldType} - */ -jspb.BinaryReader.prototype.readAny = function(fieldType) { - this.nextWireType_ = jspb.BinaryConstants.FieldTypeToWireType(fieldType); - var fieldTypes = jspb.BinaryConstants.FieldType; - switch (fieldType) { - case fieldTypes.DOUBLE: - return this.readDouble(); - case fieldTypes.FLOAT: - return this.readFloat(); - case fieldTypes.INT64: - return this.readInt64(); - case fieldTypes.UINT64: - return this.readUint64(); - case fieldTypes.INT32: - return this.readInt32(); - case fieldTypes.FIXED64: - return this.readFixed64(); - case fieldTypes.FIXED32: - return this.readFixed32(); - case fieldTypes.BOOL: - return this.readBool(); - case fieldTypes.STRING: - return this.readString(); - case fieldTypes.GROUP: - jspb.asserts.fail('Group field type not supported in readAny()'); - case fieldTypes.MESSAGE: - jspb.asserts.fail('Message field type not supported in readAny()'); - case fieldTypes.BYTES: - return this.readBytes(); - case fieldTypes.UINT32: - return this.readUint32(); - case fieldTypes.ENUM: - return this.readEnum(); - case fieldTypes.SFIXED32: - return this.readSfixed32(); - case fieldTypes.SFIXED64: - return this.readSfixed64(); - case fieldTypes.SINT32: - return this.readSint32(); - case fieldTypes.SINT64: - return this.readSint64(); - case fieldTypes.FHASH64: - return this.readFixedHash64(); - case fieldTypes.VHASH64: - return this.readVarintHash64(); - default: - jspb.asserts.fail('Invalid field type in readAny()'); - } - return 0; -}; - - -/** - * Deserialize a proto into the provided message object using the provided - * reader function. This function is templated as we currently have one client - * who is using manual deserialization instead of the code-generated versions. - * @template T - * @param {T} message - * @param {function(T, !jspb.BinaryReader)} reader - * @export - */ -jspb.BinaryReader.prototype.readMessage = function(message, reader) { - jspb.asserts.assert( - this.nextWireType_ == jspb.BinaryConstants.WireType.DELIMITED); - - // Save the current endpoint of the decoder and move it to the end of the - // embedded message. - var oldEnd = this.decoder_.getEnd(); - var length = this.decoder_.readUnsignedVarint32(); - var newEnd = this.decoder_.getCursor() + length; - this.decoder_.setEnd(newEnd); - - // Deserialize the embedded message. - reader(message, this); - - // Advance the decoder past the embedded message and restore the endpoint. - this.decoder_.setCursor(newEnd); - this.decoder_.setEnd(oldEnd); -}; + /** + * Reads a signed 32-bit fixed-length integer fiield from the binary stream, + * or throws an error if the next field in the stream is not of the correct + * wire type. + * + * @return {string} The value of the signed 32-bit integer field as a decimal + * string. + */ + readSfixed32String() { + asserts.assert(this.nextWireType_ == BinaryConstants.WireType.FIXED32); + return BinaryDecoder.readInt32(this.decoder_).toString(); + } -/** - * Deserialize a proto into the provided message object using the provided - * reader function, assuming that the message is serialized as a group - * with the given tag. - * @template T - * @param {number} field - * @param {T} message - * @param {function(T, !jspb.BinaryReader)} reader - * @export - */ -jspb.BinaryReader.prototype.readGroup = function(field, message, reader) { - // Ensure that the wire type is correct. - jspb.asserts.assert( - this.nextWireType_ == jspb.BinaryConstants.WireType.START_GROUP); - // Ensure that the field number is correct. - jspb.asserts.assert(this.nextField_ == field); - - // Deserialize the message. The deserialization will stop at an END_GROUP tag. - reader(message, this); - - if (!this.error_ && - this.nextWireType_ != jspb.BinaryConstants.WireType.END_GROUP) { - jspb.asserts.fail('Group submessage did not end with an END_GROUP tag'); - this.error_ = true; + /** + * Reads a signed 64-bit fixed-length integer fiield from the binary stream, + * or throws an error if the next field in the stream is not of the correct + * wire type. + * + * @return {number} The value of the sfixed64 field. + */ + readSfixed64() { + asserts.assert(this.nextWireType_ == BinaryConstants.WireType.FIXED64); + return BinaryDecoder.readInt64(this.decoder_); } -}; - - -/** - * Return a decoder that wraps the current delimited field. - * @return {!jspb.BinaryDecoder} - * @export - */ -jspb.BinaryReader.prototype.getFieldDecoder = function() { - jspb.asserts.assert( - this.nextWireType_ == jspb.BinaryConstants.WireType.DELIMITED); - - var length = this.decoder_.readUnsignedVarint32(); - var start = this.decoder_.getCursor(); - var end = start + length; - - var innerDecoder = - jspb.BinaryDecoder.alloc(this.decoder_.getBuffer(), start, length); - this.decoder_.setCursor(end); - return innerDecoder; -}; - - -/** - * Reads a signed 32-bit integer field from the binary stream, or throws an - * error if the next field in the stream is not of the correct wire type. - * - * @return {number} The value of the signed 32-bit integer field. - * @export - */ -jspb.BinaryReader.prototype.readInt32 = function() { - jspb.asserts.assert( - this.nextWireType_ == jspb.BinaryConstants.WireType.VARINT); - return this.decoder_.readSignedVarint32(); -}; - - -/** - * Reads a signed 32-bit integer field from the binary stream, or throws an - * error if the next field in the stream is not of the correct wire type. - * - * Returns the value as a string. - * - * @return {string} The value of the signed 32-bit integer field as a decimal - * string. - * @export - */ -jspb.BinaryReader.prototype.readInt32String = function() { - jspb.asserts.assert( - this.nextWireType_ == jspb.BinaryConstants.WireType.VARINT); - return this.decoder_.readSignedVarint32String(); -}; - - -/** - * Reads a signed 64-bit integer field from the binary stream, or throws an - * error if the next field in the stream is not of the correct wire type. - * - * @return {number} The value of the signed 64-bit integer field. - * @export - */ -jspb.BinaryReader.prototype.readInt64 = function() { - jspb.asserts.assert( - this.nextWireType_ == jspb.BinaryConstants.WireType.VARINT); - return this.decoder_.readSignedVarint64(); -}; - - -/** - * Reads a signed 64-bit integer field from the binary stream, or throws an - * error if the next field in the stream is not of the correct wire type. - * - * Returns the value as a string. - * - * @return {string} The value of the signed 64-bit integer field as a decimal - * string. - * @export - */ -jspb.BinaryReader.prototype.readInt64String = function() { - jspb.asserts.assert( - this.nextWireType_ == jspb.BinaryConstants.WireType.VARINT); - return this.decoder_.readSignedVarint64String(); -}; - - -/** - * Reads an unsigned 32-bit integer field from the binary stream, or throws an - * error if the next field in the stream is not of the correct wire type. - * - * @return {number} The value of the unsigned 32-bit integer field. - * @export - */ -jspb.BinaryReader.prototype.readUint32 = function() { - jspb.asserts.assert( - this.nextWireType_ == jspb.BinaryConstants.WireType.VARINT); - return this.decoder_.readUnsignedVarint32(); -}; - - -/** - * Reads an unsigned 32-bit integer field from the binary stream, or throws an - * error if the next field in the stream is not of the correct wire type. - * - * Returns the value as a string. - * - * @return {string} The value of the unsigned 32-bit integer field as a decimal - * string. - * @export - */ -jspb.BinaryReader.prototype.readUint32String = function() { - jspb.asserts.assert( - this.nextWireType_ == jspb.BinaryConstants.WireType.VARINT); - return this.decoder_.readUnsignedVarint32String(); -}; - - -/** - * Reads an unsigned 64-bit integer field from the binary stream, or throws an - * error if the next field in the stream is not of the correct wire type. - * - * @return {number} The value of the unsigned 64-bit integer field. - * @export - */ -jspb.BinaryReader.prototype.readUint64 = function() { - jspb.asserts.assert( - this.nextWireType_ == jspb.BinaryConstants.WireType.VARINT); - return this.decoder_.readUnsignedVarint64(); -}; - - -/** - * Reads an unsigned 64-bit integer field from the binary stream, or throws an - * error if the next field in the stream is not of the correct wire type. - * - * Returns the value as a string. - * - * @return {string} The value of the unsigned 64-bit integer field as a decimal - * string. - * @export - */ -jspb.BinaryReader.prototype.readUint64String = function() { - jspb.asserts.assert( - this.nextWireType_ == jspb.BinaryConstants.WireType.VARINT); - return this.decoder_.readUnsignedVarint64String(); -}; - - -/** - * Reads a signed zigzag-encoded 32-bit integer field from the binary stream, - * or throws an error if the next field in the stream is not of the correct - * wire type. - * - * @return {number} The value of the signed 32-bit integer field. - * @export - */ -jspb.BinaryReader.prototype.readSint32 = function() { - jspb.asserts.assert( - this.nextWireType_ == jspb.BinaryConstants.WireType.VARINT); - return this.decoder_.readZigzagVarint32(); -}; - - -/** - * Reads a signed zigzag-encoded 64-bit integer field from the binary stream, - * or throws an error if the next field in the stream is not of the correct - * wire type. - * - * @return {number} The value of the signed 64-bit integer field. - * @export - */ -jspb.BinaryReader.prototype.readSint64 = function() { - jspb.asserts.assert( - this.nextWireType_ == jspb.BinaryConstants.WireType.VARINT); - return this.decoder_.readZigzagVarint64(); -}; - - -/** - * Reads a signed zigzag-encoded 64-bit integer field from the binary stream, - * or throws an error if the next field in the stream is not of the correct - * wire type. - * - * @return {string} The value of the signed 64-bit integer field as a decimal - * string. - * @export - */ -jspb.BinaryReader.prototype.readSint64String = function() { - jspb.asserts.assert( - this.nextWireType_ == jspb.BinaryConstants.WireType.VARINT); - return this.decoder_.readZigzagVarint64String(); -}; - - -/** - * Reads an unsigned 32-bit fixed-length integer fiield from the binary stream, - * or throws an error if the next field in the stream is not of the correct - * wire type. - * - * @return {number} The value of the double field. - * @export - */ -jspb.BinaryReader.prototype.readFixed32 = function() { - jspb.asserts.assert( - this.nextWireType_ == jspb.BinaryConstants.WireType.FIXED32); - return this.decoder_.readUint32(); -}; - - -/** - * Reads an unsigned 64-bit fixed-length integer fiield from the binary stream, - * or throws an error if the next field in the stream is not of the correct - * wire type. - * - * @return {number} The value of the float field. - * @export - */ -jspb.BinaryReader.prototype.readFixed64 = function() { - jspb.asserts.assert( - this.nextWireType_ == jspb.BinaryConstants.WireType.FIXED64); - return this.decoder_.readUint64(); -}; - - -/** - * Reads a signed 64-bit integer field from the binary stream as a string, or - * throws an error if the next field in the stream is not of the correct wire - * type. - * - * Returns the value as a string. - * - * @return {string} The value of the unsigned 64-bit integer field as a decimal - * string. - * @export - */ -jspb.BinaryReader.prototype.readFixed64String = function() { - jspb.asserts.assert( - this.nextWireType_ == jspb.BinaryConstants.WireType.FIXED64); - return this.decoder_.readUint64String(); -}; - - -/** - * Reads a signed 32-bit fixed-length integer fiield from the binary stream, or - * throws an error if the next field in the stream is not of the correct wire - * type. - * - * @return {number} The value of the signed 32-bit integer field. - * @export - */ -jspb.BinaryReader.prototype.readSfixed32 = function() { - jspb.asserts.assert( - this.nextWireType_ == jspb.BinaryConstants.WireType.FIXED32); - return this.decoder_.readInt32(); -}; - - -/** - * Reads a signed 32-bit fixed-length integer fiield from the binary stream, or - * throws an error if the next field in the stream is not of the correct wire - * type. - * - * @return {string} The value of the signed 32-bit integer field as a decimal - * string. - * @export - */ -jspb.BinaryReader.prototype.readSfixed32String = function() { - jspb.asserts.assert( - this.nextWireType_ == jspb.BinaryConstants.WireType.FIXED32); - return this.decoder_.readInt32().toString(); -}; - - -/** - * Reads a signed 64-bit fixed-length integer fiield from the binary stream, or - * throws an error if the next field in the stream is not of the correct wire - * type. - * - * @return {number} The value of the sfixed64 field. - * @export - */ -jspb.BinaryReader.prototype.readSfixed64 = function() { - jspb.asserts.assert( - this.nextWireType_ == jspb.BinaryConstants.WireType.FIXED64); - return this.decoder_.readInt64(); -}; - - -/** - * Reads a signed 64-bit fixed-length integer fiield from the binary stream, or - * throws an error if the next field in the stream is not of the correct wire - * type. - * - * Returns the value as a string. - * - * @return {string} The value of the sfixed64 field as a decimal string. - * @export - */ -jspb.BinaryReader.prototype.readSfixed64String = function() { - jspb.asserts.assert( - this.nextWireType_ == jspb.BinaryConstants.WireType.FIXED64); - return this.decoder_.readInt64String(); -}; - - -/** - * Reads a 32-bit floating-point field from the binary stream, or throws an - * error if the next field in the stream is not of the correct wire type. - * - * @return {number} The value of the float field. - * @export - */ -jspb.BinaryReader.prototype.readFloat = function() { - jspb.asserts.assert( - this.nextWireType_ == jspb.BinaryConstants.WireType.FIXED32); - return this.decoder_.readFloat(); -}; - - -/** - * Reads a 64-bit floating-point field from the binary stream, or throws an - * error if the next field in the stream is not of the correct wire type. - * - * @return {number} The value of the double field. - * @export - */ -jspb.BinaryReader.prototype.readDouble = function() { - jspb.asserts.assert( - this.nextWireType_ == jspb.BinaryConstants.WireType.FIXED64); - return this.decoder_.readDouble(); -}; -/** - * Reads a boolean field from the binary stream, or throws an error if the next - * field in the stream is not of the correct wire type. - * - * @return {boolean} The value of the boolean field. - * @export - */ -jspb.BinaryReader.prototype.readBool = function() { - jspb.asserts.assert( - this.nextWireType_ == jspb.BinaryConstants.WireType.VARINT); - return !!this.decoder_.readUnsignedVarint32(); -}; - - -/** - * Reads an enum field from the binary stream, or throws an error if the next - * field in the stream is not of the correct wire type. - * - * @return {number} The value of the enum field. - * @export - */ -jspb.BinaryReader.prototype.readEnum = function() { - jspb.asserts.assert( - this.nextWireType_ == jspb.BinaryConstants.WireType.VARINT); - return this.decoder_.readSignedVarint64(); -}; - - -/** - * Reads a string field from the binary stream, or throws an error if the next - * field in the stream is not of the correct wire type. - * - * @return {string} The value of the string field. - * @export - */ -jspb.BinaryReader.prototype.readString = function() { - // delegate to the other reader so that inlining can eliminate this method - // in the common case. - if (UTF8_PARSING_ERRORS_ARE_FATAL) { - return this.readStringRequireUtf8(); + /** + * Reads a signed 64-bit fixed-length integer field from the binary stream, + * or throws an error if the next field in the stream is not of the correct + * wire type. + * + * Returns the value as a string. + * + * @return {string} The value of the sfixed64 field as a decimal string. + */ + readSfixed64String() { + asserts.assert(this.nextWireType_ == BinaryConstants.WireType.FIXED64); + return BinaryDecoder.readInt64String(this.decoder_); } - jspb.asserts.assert( - this.nextWireType_ == jspb.BinaryConstants.WireType.DELIMITED); - var length = this.decoder_.readUnsignedVarint32(); - return this.decoder_.readString(length, /*requireUtf8=*/ false); -}; - -/** - * Reads a string field from the binary stream, or throws an error if the next - * field in the stream is not of the correct wire type, or if the string is - * not valid utf8. - * - * @return {string} The value of the string field. - */ -jspb.BinaryReader.prototype.readStringRequireUtf8 = function () { - jspb.asserts.assert(this.nextWireType_ == jspb.BinaryConstants.WireType.DELIMITED); - const length = this.decoder_.readUnsignedVarint32(); - return this.decoder_.readString(length, /*requireUtf8=*/ true); -}; - - -/** - * 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. - * @export - */ -jspb.BinaryReader.prototype.readBytes = function() { - jspb.asserts.assert( - this.nextWireType_ == jspb.BinaryConstants.WireType.DELIMITED); - var length = this.decoder_.readUnsignedVarint32(); - return this.decoder_.readBytes(length); -}; - - -/** - * 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. - * - * @return {string} The hash value. - * @export - */ -jspb.BinaryReader.prototype.readVarintHash64 = function() { - jspb.asserts.assert( - this.nextWireType_ == jspb.BinaryConstants.WireType.VARINT); - return this.decoder_.readVarintHash64(); -}; - - -/** - * 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. - * @export - */ -jspb.BinaryReader.prototype.readSintHash64 = function() { - jspb.asserts.assert( - this.nextWireType_ == jspb.BinaryConstants.WireType.VARINT); - return this.decoder_.readZigzagVarintHash64(); -}; + /** + * Reads a 32-bit floating-point field from the binary stream, or throws an + * error if the next field in the stream is not of the correct wire type. + * + * @return {number} The value of the float field. + */ + readFloat() { + asserts.assert(this.nextWireType_ == BinaryConstants.WireType.FIXED32); + return BinaryDecoder.readFloat(this.decoder_); + } -/** - * 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. - * - * @param {function(number, number): T} convert Conversion function to produce - * the result value, takes parameters (lowBits, highBits). - * @return {T} - * @template T - * @export - */ -jspb.BinaryReader.prototype.readSplitVarint64 = function(convert) { - jspb.asserts.assert( - this.nextWireType_ == jspb.BinaryConstants.WireType.VARINT); - return this.decoder_.readSplitVarint64(convert); -}; + /** + * Reads a 64-bit floating-point field from the binary stream, or throws an + * error if the next field in the stream is not of the correct wire type. + * + * @return {number} The value of the double field. + */ + readDouble() { + asserts.assert(this.nextWireType_ == BinaryConstants.WireType.FIXED64); + return BinaryDecoder.readDouble(this.decoder_); + } -/** - * 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 - * @export - */ -jspb.BinaryReader.prototype.readSplitZigzagVarint64 = function(convert) { - jspb.asserts.assert( - this.nextWireType_ == jspb.BinaryConstants.WireType.VARINT); - return this.decoder_.readSplitVarint64(function(lowBits, highBits) { - return jspb.utils.fromZigzag64(lowBits, highBits, convert); - }); -}; + /** + * Reads a boolean field from the binary stream, or throws an error if the + * next field in the stream is not of the correct wire type. + * + * @return {boolean} The value of the boolean field. + */ + readBool() { + asserts.assert(this.nextWireType_ == BinaryConstants.WireType.VARINT); + return BinaryDecoder.readBool(this.decoder_); + } -/** - * 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. - * - * @return {string} The hash value. - * @export - */ -jspb.BinaryReader.prototype.readFixedHash64 = function() { - jspb.asserts.assert( - this.nextWireType_ == jspb.BinaryConstants.WireType.FIXED64); - return this.decoder_.readFixedHash64(); -}; + /** + * Reads an enum field from the binary stream, or throws an error if the next + * field in the stream is not of the correct wire type. + * + * @return {number} The value of the enum field. + */ + readEnum() { + asserts.assert(this.nextWireType_ == BinaryConstants.WireType.VARINT); + return BinaryDecoder.readSignedVarint32(this.decoder_); + } -/** - * Reads a 64-bit fixed64 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 - * @export - */ -jspb.BinaryReader.prototype.readSplitFixed64 = function(convert) { - jspb.asserts.assert( - this.nextWireType_ == jspb.BinaryConstants.WireType.FIXED64); - return this.decoder_.readSplitFixed64(convert); -}; + /** + * Reads a string field from the binary stream, or throws an error if the next + * field in the stream is not of the correct wire type. + * + * @return {string} The value of the string field. + */ + readString() { + // delegate to the other reader so that inlining can eliminate this method + // in the common case. + if (UTF8_PARSING_ERRORS_ARE_FATAL) { + return this.readStringRequireUtf8(); + } + asserts.assert(this.nextWireType_ == BinaryConstants.WireType.DELIMITED); + const length = BinaryDecoder.readUnsignedVarint32(this.decoder_); + return this.decoder_.readString(length, /*parsingErrorsAreFatal=*/ false); + } + /** + * Reads a string field from the binary stream, or throws an error if the next + * field in the stream is not of the correct wire type, or if the string is + * not valid utf8. + * + * @return {string} The value of the string field. + */ + readStringRequireUtf8() { + asserts.assert(this.nextWireType_ == BinaryConstants.WireType.DELIMITED); + const length = BinaryDecoder.readUnsignedVarint32(this.decoder_); + return this.decoder_.readString(length, /*parsingErrorsAreFatal=*/ true); + } -/** - * Reads a packed scalar field using the supplied raw reader function. - * @param {function(this:jspb.BinaryDecoder)} decodeMethod - * @return {!Array} - * @private - */ -jspb.BinaryReader.prototype.readPackedField_ = function(decodeMethod) { - jspb.asserts.assert( - this.nextWireType_ == jspb.BinaryConstants.WireType.DELIMITED); - var length = this.decoder_.readUnsignedVarint32(); - var end = this.decoder_.getCursor() + length; - var result = []; - while (this.decoder_.getCursor() < end) { - // TODO(aappleby): .call is slow - result.push(decodeMethod.call(this.decoder_)); - } - return result; -}; + /** + * 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. + */ + readBytes() { + asserts.assert(this.nextWireType_ == BinaryConstants.WireType.DELIMITED); + const length = BinaryDecoder.readUnsignedVarint32(this.decoder_); + return this.decoder_.readBytes(length); + } + /** + * 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 {!ByteString} The block of bytes. + */ + readByteString() { + asserts.assert(this.nextWireType_ == BinaryConstants.WireType.DELIMITED); + const length = BinaryDecoder.readUnsignedVarint32(this.decoder_); + return this.decoder_.readByteString(length); + } -/** - * Reads a packed int32 field, which consists of a length header and a list of - * signed varints. - * @return {!Array} - * @export - */ -jspb.BinaryReader.prototype.readPackedInt32 = function() { - return this.readPackedField_(this.decoder_.readSignedVarint32); -}; + /** + * 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. + * + * @param {function(number, number): T} convert Conversion function to produce + * the result value, takes parameters (lowBits, highBits). + * @return {T} + * @template T + */ + readSplitVarint64(convert) { + asserts.assert(this.nextWireType_ == BinaryConstants.WireType.VARINT); + return BinaryDecoder.readSplitVarint64(this.decoder_, convert); + } -/** - * Reads a packed int32 field, which consists of a length header and a list of - * signed varints. Returns a list of strings. - * @return {!Array} - * @export - */ -jspb.BinaryReader.prototype.readPackedInt32String = function() { - return this.readPackedField_(this.decoder_.readSignedVarint32String); -}; + /** + * 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 + */ + readSplitZigzagVarint64(convert) { + asserts.assert(this.nextWireType_ == BinaryConstants.WireType.VARINT); + return BinaryDecoder.readSplitVarint64( + this.decoder_, + (lowBits, highBits) => utils.fromZigzag64(lowBits, highBits, convert)); + } -/** - * Reads a packed int64 field, which consists of a length header and a list of - * signed varints. - * @return {!Array} - * @export - */ -jspb.BinaryReader.prototype.readPackedInt64 = function() { - return this.readPackedField_(this.decoder_.readSignedVarint64); -}; + /** + * Reads a 64-bit fixed64 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 + */ + readSplitFixed64(convert) { + asserts.assert(this.nextWireType_ == BinaryConstants.WireType.FIXED64); + return BinaryDecoder.readSplitFixed64(this.decoder_, convert); + } -/** - * Reads a packed int64 field, which consists of a length header and a list of - * signed varints. Returns a list of strings. - * @return {!Array} - * @export - */ -jspb.BinaryReader.prototype.readPackedInt64String = function() { - return this.readPackedField_(this.decoder_.readSignedVarint64String); -}; + /** + * Reads a packed scalar field using the supplied raw reader function. + * @param {function(!BinaryDecoder):T} decodeMethod + * @param {!Array} dst + * @template T + * @private + */ + readPackedFieldInto_(decodeMethod, dst) { + const length = this.readPackedFieldLength_(); + const end = this.decoder_.getCursor() + length; + while (this.decoder_.getCursor() < end) { + dst.push(decodeMethod(this.decoder_)); + } + } + /** + * Reads the length of a packed field. + * @return {number} + * @private + */ + readPackedFieldLength_() { + asserts.assert(this.nextWireType_ == BinaryConstants.WireType.DELIMITED); + return BinaryDecoder.readUnsignedVarint32(this.decoder_); + } -/** - * Reads a packed uint32 field, which consists of a length header and a list of - * unsigned varints. - * @return {!Array} - * @export - */ -jspb.BinaryReader.prototype.readPackedUint32 = function() { - return this.readPackedField_(this.decoder_.readUnsignedVarint32); -}; + /** + * Reads an int32 field that might be packed + * @param {!Array} dst + */ + readPackableInt32Into(dst) { + if (this.isDelimited()) { + this.readPackedFieldInto_(BinaryDecoder.readSignedVarint32, dst); + } else { + dst.push(this.readInt32()); + } + } + /** + * Reads an int64 field that might be packed + * @param {!Array}dst + */ + readPackableInt64Into(dst) { + if (this.isDelimited()) { + this.readPackedFieldInto_(BinaryDecoder.readSignedVarint64, dst); + } else { + dst.push(this.readInt64()); + } + } -/** - * Reads a packed uint32 field, which consists of a length header and a list of - * unsigned varints. Returns a list of strings. - * @return {!Array} - * @export - */ -jspb.BinaryReader.prototype.readPackedUint32String = function() { - return this.readPackedField_(this.decoder_.readUnsignedVarint32String); -}; + /** + * Reads an int64 field that might be packed + * @param {!Array} dst + */ + readPackableInt64StringInto(dst) { + if (this.isDelimited()) { + this.readPackedFieldInto_(BinaryDecoder.readSignedVarint64String, dst); + } else { + dst.push(this.readInt64String()); + } + } + /** + * Reads an int32 field that might be packed + * @param {!Array} dst + */ + readPackableUint32Into(dst) { + if (this.isDelimited()) { + this.readPackedFieldInto_(BinaryDecoder.readUnsignedVarint32, dst); + } else { + dst.push(this.readUint32()); + } + } -/** - * Reads a packed uint64 field, which consists of a length header and a list of - * unsigned varints. - * @return {!Array} - * @export - */ -jspb.BinaryReader.prototype.readPackedUint64 = function() { - return this.readPackedField_(this.decoder_.readUnsignedVarint64); -}; + /** + * Reads an uint64 field that might be packed + * @param {!Array} dst + */ + readPackableUint64Into(dst) { + if (this.isDelimited()) { + this.readPackedFieldInto_(BinaryDecoder.readUnsignedVarint64, dst); + } else { + dst.push(this.readUint64()); + } + } + /** + * Reads an uint64 field that might be packed + * @param {!Array} dst + */ + readPackableUint64StringInto(dst) { + if (this.isDelimited()) { + this.readPackedFieldInto_(BinaryDecoder.readUnsignedVarint64String, dst); + } else { + dst.push(this.readUint64String()); + } + } -/** - * Reads a packed uint64 field, which consists of a length header and a list of - * unsigned varints. Returns a list of strings. - * @return {!Array} - * @export - */ -jspb.BinaryReader.prototype.readPackedUint64String = function() { - return this.readPackedField_(this.decoder_.readUnsignedVarint64String); -}; + /** + * Reads a possibly packed sint32 field, + * @param {!Array} dst + */ + readPackableSint32Into(dst) { + if (this.isDelimited()) { + this.readPackedFieldInto_(BinaryDecoder.readZigzagVarint32, dst); + } else { + dst.push(this.readSint32()); + } + } + /** + * Reads a possibly packed sint64 field, + * @param {!Array} dst + */ + readPackableSint64Into(dst) { + if (this.isDelimited()) { + this.readPackedFieldInto_(BinaryDecoder.readZigzagVarint64, dst); + } else { + dst.push(this.readSint64()); + } + } -/** - * Reads a packed sint32 field, which consists of a length header and a list of - * zigzag varints. - * @return {!Array} - * @export - */ -jspb.BinaryReader.prototype.readPackedSint32 = function() { - return this.readPackedField_(this.decoder_.readZigzagVarint32); -}; + /** + * Reads a possibly packed sint64 field as an array of strings. + * @param {!Array} dst + */ + readPackableSint64StringInto(dst) { + if (this.isDelimited()) { + this.readPackedFieldInto_(BinaryDecoder.readZigzagVarint64String, dst); + } else { + dst.push(this.readSint64String()); + } + } + /** + * Reads a possibly packed fixed32 field. + * @param {!Array} dst + */ + readPackableFixed32Into(dst) { + if (this.isDelimited()) { + this.readPackedFieldInto_(BinaryDecoder.readUint32, dst); + } else { + dst.push(this.readFixed32()); + } + } -/** - * Reads a packed sint64 field, which consists of a length header and a list of - * zigzag varints. - * @return {!Array} - * @export - */ -jspb.BinaryReader.prototype.readPackedSint64 = function() { - return this.readPackedField_(this.decoder_.readZigzagVarint64); -}; + /** + * Reads a possibly packed fixed64 field. + * @param {!Array} dst + */ + readPackableFixed64Into(dst) { + if (this.isDelimited()) { + this.readPackedFieldInto_(BinaryDecoder.readUint64, dst); + } else { + dst.push(this.readFixed64()); + } + } + /** + * Reads a possibly packed fixed64 field as an array fo strings + * @param {!Array} dst + */ + readPackableFixed64StringInto(dst) { + if (this.isDelimited()) { + this.readPackedFieldInto_(BinaryDecoder.readUint64String, dst); + } else { + dst.push(this.readFixed64String()); + } + } -/** - * Reads a packed sint64 field, which consists of a length header and a list of - * zigzag varints. Returns a list of strings. - * @return {!Array} - * @export - */ -jspb.BinaryReader.prototype.readPackedSint64String = function() { - return this.readPackedField_(this.decoder_.readZigzagVarint64String); -}; + /** + * Reads a possibly packed sfixed32 + * @param {!Array} dst + */ + readPackableSfixed32Into(dst) { + if (this.isDelimited()) { + this.readPackedFieldInto_(BinaryDecoder.readInt32, dst); + } else { + dst.push(this.readSfixed32()); + } + } + /** + * Reads a possibly packed sfixed64 field. + * @param {!Array} dst + */ + readPackableSfixed64Into(dst) { + if (this.isDelimited()) { + this.readPackedFieldInto_(BinaryDecoder.readInt64, dst); + } else { + dst.push(this.readSfixed64()); + } + } -/** - * Reads a packed fixed32 field, which consists of a length header and a list - * of unsigned 32-bit ints. - * @return {!Array} - * @export - */ -jspb.BinaryReader.prototype.readPackedFixed32 = function() { - return this.readPackedField_(this.decoder_.readUint32); -}; + /** + * Reads a possibly packed sfixed64 field as an array of strings + * @param {!Array} dst + */ + readPackableSfixed64StringInto(dst) { + if (this.isDelimited()) { + this.readPackedFieldInto_(BinaryDecoder.readInt64String, dst); + } else { + dst.push(this.readSfixed64String()); + } + } + /** + * Reads a packed fixed32 field, which consists of a length header and a list + * of unsigned 32-bit ints. + * @return {!Array} + * @export + */ + readPackedFixed32() { + const values = []; + this.readPackableFixed32Into(values); + return values; + } -/** - * Reads a packed fixed64 field, which consists of a length header and a list - * of unsigned 64-bit ints. - * @return {!Array} - * @export - */ -jspb.BinaryReader.prototype.readPackedFixed64 = function() { - return this.readPackedField_(this.decoder_.readUint64); -}; + /** + * Reads a packed fixed64 field, which consists of a length header and a list + * of unsigned 64-bit ints. + * @return {!Array} + * @export + */ + readPackedFixed64() { + const values = []; + this.readPackableFixed64Into(values); + return values; + } -/** - * Reads a packed fixed64 field, which consists of a length header and a list - * of unsigned 64-bit ints. Returns a list of strings. - * @return {!Array} - * @export - */ -jspb.BinaryReader.prototype.readPackedFixed64String = function() { - return this.readPackedField_(this.decoder_.readUint64String); -}; + /** + * Reads a packed fixed64 field, which consists of a length header and a list + * of unsigned 64-bit ints. Returns a list of strings. + * @return {!Array} + * @export + */ + readPackedFixed64String() { + const values = []; + this.readPackableFixed64StringInto(values); + return values; + } -/** - * Reads a packed sfixed32 field, which consists of a length header and a list - * of 32-bit ints. - * @return {!Array} - * @export - */ -jspb.BinaryReader.prototype.readPackedSfixed32 = function() { - return this.readPackedField_(this.decoder_.readInt32); -}; + /** + * Reads a packed sfixed32 field, which consists of a length header and a list + * of 32-bit ints. + * @return {!Array} + * @export + */ + readPackedSfixed32() { + const values = []; + this.readPackableSfixed32Into(values); + return values; + } -/** - * Reads a packed sfixed64 field, which consists of a length header and a list - * of 64-bit ints. - * @return {!Array} - * @export - */ -jspb.BinaryReader.prototype.readPackedSfixed64 = function() { - return this.readPackedField_(this.decoder_.readInt64); -}; + /** + * Reads a packed sfixed64 field, which consists of a length header and a list + * of 64-bit ints. Returns a list of strings. + * @return {!Array} + * @export + */ + readPackedSfixed64String() { + const values = []; + this.readPackableSfixed64StringInto(values); + return values; + } -/** - * Reads a packed sfixed64 field, which consists of a length header and a list - * of 64-bit ints. Returns a list of strings. - * @return {!Array} - * @export - */ -jspb.BinaryReader.prototype.readPackedSfixed64String = function() { - return this.readPackedField_(this.decoder_.readInt64String); -}; + /** + * Reads a possibly packed float field + * @param {!Array} dst + */ + readPackableFloatInto(dst) { + if (this.isDelimited()) { + this.readPackedFieldInto_(BinaryDecoder.readFloat, dst); + } else { + dst.push(this.readFloat()); + } + } -/** - * Reads a packed float field, which consists of a length header and a list of - * floats. - * @return {!Array} - * @export - */ -jspb.BinaryReader.prototype.readPackedFloat = function() { - return this.readPackedField_(this.decoder_.readFloat); -}; + /** + * Reads a possibly packed double field and appends it to `dst`. + * @param {!Array} dst + */ + readPackableDoubleInto(dst) { + if (this.isDelimited()) { + this.decoder_.readDoubleArrayInto(this.readPackedFieldLength_() / 8, dst); + } else { + dst.push(this.readDouble()); + } + } + /** + * Reads a possibly packed bool field + * @param {!Array} dst + */ + readPackableBoolInto(dst) { + if (this.isDelimited()) { + this.readPackedFieldInto_(BinaryDecoder.readBool, dst); + } else { + dst.push(this.readBool()); + } + } -/** - * Reads a packed double field, which consists of a length header and a list of - * doubles. - * @return {!Array} - * @export - */ -jspb.BinaryReader.prototype.readPackedDouble = function() { - return this.readPackedField_(this.decoder_.readDouble); -}; + /** + * Reads a possibly packed enum field + * @param {!Array} dst + */ + readPackableEnumInto(dst) { + if (this.isDelimited()) { + this.readPackedFieldInto_(BinaryDecoder.readEnum, dst); + } else { + dst.push(this.readEnum()); + } + } -/** - * Reads a packed bool field, which consists of a length header and a list of - * unsigned varints. - * @return {!Array} - * @export - */ -jspb.BinaryReader.prototype.readPackedBool = function() { - return this.readPackedField_(this.decoder_.readBool); -}; + /** + * Visible for testing. + * @package + */ + static resetInstanceCache() { + BinaryReader.instanceCache_ = []; + } + /** + * Visible for testing. + * @return {!Array} + * @package + */ + static getInstanceCache() { + return BinaryReader.instanceCache_; + } +} /** - * Reads a packed enum field, which consists of a length header and a list of - * unsigned varints. - * @return {!Array} - * @export + * @param {number} tag + * @return {!BinaryConstants.WireType} */ -jspb.BinaryReader.prototype.readPackedEnum = function() { - return this.readPackedField_(this.decoder_.readEnum); -}; - +function parseWireType(tag) { + return /** @type {!BinaryConstants.WireType} */ (tag & 0x7); +} /** - * Reads a packed varint hash64 field, which consists of a length header and a - * list of varint hash64s. - * @return {!Array} - * @export + * @param {number} tag + * @return {number} */ -jspb.BinaryReader.prototype.readPackedVarintHash64 = function() { - return this.readPackedField_(this.decoder_.readVarintHash64); -}; - +function parseFieldNumber(tag) { + return tag >>> 3; +} /** - * Reads a packed fixed hash64 field, which consists of a length header and a - * list of fixed hash64s. - * @return {!Array} - * @export - */ -jspb.BinaryReader.prototype.readPackedFixedHash64 = function() { - return this.readPackedField_(this.decoder_.readFixedHash64); + * Global pool of BinaryReader instances. + * @private {!Array} + */ +BinaryReader.instanceCache_ = []; + +const /** number */ MESSAGE_SET_START_GROUP_TAG = utils.makeTag( + BinaryConstants.MESSAGE_SET_GROUP_NUMBER, + BinaryConstants.WireType.START_GROUP); +const /** number */ MESSAGE_SET_TYPE_ID_TAG = utils.makeTag( + BinaryConstants.MESSAGE_SET_TYPE_ID_FIELD_NUMBER, + BinaryConstants.WireType.VARINT); +const /** number */ MESSAGE_SET_MESSAGE_TAG = utils.makeTag( + BinaryConstants.MESSAGE_SET_MESSAGE_FIELD_NUMBER, + BinaryConstants.WireType.DELIMITED); +const /** number */ MESSAGE_SET_END_TAG = utils.makeTag( + BinaryConstants.MESSAGE_SET_GROUP_NUMBER, + BinaryConstants.WireType.END_GROUP); + +exports = { + BinaryReader, + BinaryReaderOptions, + UTF8_PARSING_ERRORS_ARE_FATAL, }; diff --git a/binary/reader_alias.js b/binary/reader_alias.js new file mode 100755 index 0000000..c19d104 --- /dev/null +++ b/binary/reader_alias.js @@ -0,0 +1,9 @@ +/** + * @fileoverview Legacy alias for the old namespace used by reader.js + */ +goog.module('jspb.BinaryReader'); +goog.module.declareLegacyNamespace(); + +const {BinaryReader} = goog.require('jspb.binary.reader'); + +exports = BinaryReader; diff --git a/binary/reader_test.js b/binary/reader_test.js old mode 100644 new mode 100755 index fcb32e2..5df6235 --- a/binary/reader_test.js +++ b/binary/reader_test.js @@ -42,82 +42,193 @@ */ goog.require('jspb.BinaryConstants'); -goog.require('jspb.BinaryDecoder'); +goog.require('jspb.binary.decoder'); +goog.require('jspb.binary.encoder'); goog.require('jspb.BinaryReader'); goog.require('jspb.BinaryWriter'); goog.require('jspb.utils'); -goog.requireType('jspb.BinaryMessage'); +const BinaryConstants = goog.module.get('jspb.BinaryConstants'); +const BinaryMessage = BinaryConstants.BinaryMessage; +const BinaryDecoder = goog.module.get('jspb.binary.decoder').BinaryDecoder; +const BinaryEncoder = goog.module.get('jspb.binary.encoder').BinaryEncoder; +const BinaryReader = goog.module.get('jspb.BinaryReader'); +const BinaryWriter = goog.module.get('jspb.BinaryWriter'); +const makeTag = goog.module.get('jspb.utils').makeTag; +const sliceUint8Array = goog.module.get('jspb.utils').sliceUint8Array; + +const test64BitIntSignedData = [ + '-9223372036854775808', + '-4611686018427387904', + 4294967296, + -2147483648, + -1, + 0, + 1, + 2147483648, + 4294967296, + '4611686018427387904', + '9223372036854775807', +]; + +const test64BitIntUnsignedData = [ + 0, + 1, + 2147483648, + 4294967296, + '4611686018427387904', + '9223372036854775808', + '18446744073709551615', +]; +/** + * @param {number|string|bigint} x + * @returns number + */ +function asNumberOrString(x) { + const num = Number(x); + return Number.isSafeInteger(num) ? num : (/** @type{number} */(String(x))); +} + +function doTest64BitIntField( + /** function(this:!BinaryReader): number */ readField, + /** function(this:!BinaryWriter, number, number) */writeField, + /** ReadonlyArray */testData, +) { + const writer = new BinaryWriter(); + + const inputValues = []; + + for (const cursor of testData) { + writeField.call(writer, 1, /** @type{number}*/(cursor)); + inputValues.push({ + fieldNumber: 1, + value: cursor, + }); + } + + const reader = BinaryReader.alloc(writer.getResultBuffer()); + + for (let i = 0; i < inputValues.length; i++) { + const expected = inputValues[i]; + reader.nextField(); + expect(reader.getFieldNumber()).toBe(expected.fieldNumber); + expect(readField.call(reader)).toBe(/** @type{number}*/(expected.value)); + } +} + +function doTestSigned64BitIntField( + /** function(this:!BinaryReader): number */ readField, + /** function(this:!BinaryWriter, number, number) */writeField +) { + doTest64BitIntField(readField, writeField, test64BitIntSignedData); + + // Encoding values outside should truncate. + const outOfRangeData = ['-36893488147419103230', '36893488147419103230']; + const writer = new BinaryWriter(); + + for (const value of outOfRangeData) { + writeField.call(writer, 1, /** @type{number} */(value)); + } + + const reader = BinaryReader.alloc(writer.getResultBuffer()); + for (const value of outOfRangeData) { + reader.nextField(); + expect(reader.getFieldNumber()).toBe(1); + expect(readField.call(reader)).toBe( + asNumberOrString(BigInt.asIntN(64, BigInt(value)).toString()), + ); + } +} + +function doTestUnsigned64BitIntField( + /** function(this:!BinaryReader): number */ readField, + /** function(this:!BinaryWriter, number, number) */writeField +) { + doTest64BitIntField(readField, writeField, test64BitIntUnsignedData); + + // Out of range numbers should assert. + const pastLowerLimit = -1; + const writer = new BinaryWriter(); + expect(() => void writeField.call(writer, 1, pastLowerLimit)).toThrow(); + + // Out of range strings should truncate. + const pastUpperLimit = '36893488147419103230'; + writeField.call(writer, 1, /** @type{number} */(pastUpperLimit)); + + const reader = BinaryReader.alloc(writer.getResultBuffer()); + reader.nextField(); + expect(reader.getFieldNumber()).toBe(1); + expect(readField.call(reader)).toBe( + asNumberOrString(BigInt.asUintN(64, BigInt(pastUpperLimit)).toString()), + ); +} describe('binaryReaderTest', () => { - /** - * Tests the reader instance cache. - */ - it('testInstanceCaches', /** @suppress {visibility} */ () => { - const writer = new jspb.BinaryWriter(); - const dummyMessage = /** @type {!jspb.BinaryMessage} */ ({}); - writer.writeMessage(1, dummyMessage, () => {}); - writer.writeMessage(2, dummyMessage, () => {}); + /** Tests the reader instance cache. */ + it('testInstanceCaches', () => { + const writer = new BinaryWriter(); + const dummyMessage = /** @type {!BinaryMessage} */ ({}); + writer.writeMessage(1, dummyMessage, () => { }); + writer.writeMessage(2, dummyMessage, () => { }); const buffer = writer.getResultBuffer(); // Empty the instance caches. - jspb.BinaryReader.clearInstanceCache(); + BinaryReader.resetInstanceCache(); // Allocating and then freeing three decoders should leave us with three in // the cache. - const decoder1 = jspb.BinaryDecoder.alloc(); - const decoder2 = jspb.BinaryDecoder.alloc(); - const decoder3 = jspb.BinaryDecoder.alloc(); + const decoder1 = BinaryDecoder.alloc(); + const decoder2 = BinaryDecoder.alloc(); + const decoder3 = BinaryDecoder.alloc(); decoder1.free(); decoder2.free(); decoder3.free(); - expect(jspb.BinaryDecoder.getInstanceCacheLength()).toEqual(3); - expect(jspb.BinaryReader.getInstanceCacheLength()).toEqual(0); + expect(BinaryDecoder.getInstanceCache().length).toEqual(3); + expect(BinaryReader.getInstanceCache().length).toEqual(0); // Allocating and then freeing a reader should remove one decoder from its // cache, but it should stay stuck to the reader afterwards since we can't // have a reader without a decoder. - jspb.BinaryReader.alloc().free(); + BinaryReader.alloc().free(); - expect(jspb.BinaryDecoder.getInstanceCacheLength()).toEqual(2); - expect(jspb.BinaryReader.getInstanceCacheLength()).toEqual(1); + expect(BinaryDecoder.getInstanceCache().length).toEqual(2); + expect(BinaryReader.getInstanceCache().length).toEqual(1); // Allocating a reader should remove a reader from the cache. - const reader = jspb.BinaryReader.alloc(buffer); + const reader = BinaryReader.alloc(buffer); - expect(jspb.BinaryDecoder.getInstanceCacheLength()).toEqual(2); - expect(jspb.BinaryReader.getInstanceCacheLength()).toEqual(0); + expect(BinaryDecoder.getInstanceCache().length).toEqual(2); + expect(BinaryReader.getInstanceCache().length).toEqual(0); // Processing the message reuses the current reader. reader.nextField(); expect(reader.getFieldNumber()).toEqual(1); reader.readMessage(dummyMessage, () => { - expect(jspb.BinaryReader.getInstanceCacheLength()).toEqual(0); - }); + expect(BinaryReader.getInstanceCache().length).toEqual(0); + }); reader.nextField(); expect(reader.getFieldNumber()).toEqual(2); reader.readMessage(dummyMessage, () => { - expect(jspb.BinaryReader.getInstanceCacheLength()).toEqual(0); - }); + expect(BinaryReader.getInstanceCache().length).toEqual(0); + }); expect(reader.nextField()).toEqual(false); - expect(jspb.BinaryDecoder.getInstanceCacheLength()).toEqual(2); - expect(jspb.BinaryReader.getInstanceCacheLength()).toEqual(0); + expect(BinaryDecoder.getInstanceCache().length).toEqual(2); + expect(BinaryReader.getInstanceCache().length).toEqual(0); // Freeing the reader should put it back into the cache. reader.free(); - expect(jspb.BinaryDecoder.getInstanceCacheLength()).toEqual(2); - expect(jspb.BinaryReader.getInstanceCacheLength()).toEqual(1); + expect(BinaryDecoder.getInstanceCache().length).toEqual(2); + expect(BinaryReader.getInstanceCache().length).toEqual(1); }); - /** * @param {number} x * @return {number} @@ -128,332 +239,273 @@ describe('binaryReaderTest', () => { return temp[0]; } + /** Verifies that misuse of the reader class triggers assertions. */ + it('testReadErrors', /** @suppress {checkTypes|visibility} */() => { + // Calling readMessage on a non-delimited field should trigger an + // assertion. + let reader = BinaryReader.alloc([8, 1]); + const dummyMessage = /** @type {!BinaryMessage} */ ({}); + reader.nextField(); + expect(() => { + reader.readMessage(dummyMessage, goog.nullFunction); + }).toThrowError(); - /** - * Verifies that misuse of the reader class triggers assertions. - */ - it('testReadErrors', /** @suppress {checkTypes|visibility} */ () => { - // Calling readMessage on a non-delimited field should trigger an - // assertion. - let reader = jspb.BinaryReader.alloc([8, 1]); - const dummyMessage = /** @type {!jspb.BinaryMessage} */ ({}); - reader.nextField(); - expect(() => { - reader.readMessage(dummyMessage, () => { }); - }).toThrow(); - - // Reading past the end of the stream should trigger an assertion. - reader = jspb.BinaryReader.alloc([9, 1]); - reader.nextField(); - expect(() => { - reader.readFixed64(); - }).toThrow(); + // Reading past the end of the stream should trigger an assertion. + reader = BinaryReader.alloc([9, 1]); + reader.nextField(); + expect(() => reader.readFixed64()).toThrowError(); - // Reading past the end of a submessage should trigger an assertion. - reader = jspb.BinaryReader.alloc([10, 4, 13, 1, 1, 1]); - reader.nextField(); + // Reading past the end of a submessage should trigger an assertion. + reader = BinaryReader.alloc([10, 4, 13, 1, 1, 1]); + reader.nextField(); + expect(() => { reader.readMessage(dummyMessage, () => { - reader.nextField(); - expect(() => { - reader.readFixed32(); - }).toThrow(); - }); - - // Skipping an invalid field should trigger an assertion. - reader = jspb.BinaryReader.alloc([12, 1]); - reader.nextWireType_ = 1000; - expect(() => { - reader.skipField(); - }).toThrow(); + reader.nextField(); + expect(() => reader.readFixed32()) + .toThrowError('Tried to read past the end of the data 7 > 6'); + }); + }) + .toThrowError( + 'Message parsing ended unexpectedly. Expected to read 4 bytes, instead read 5 bytes, either the data ended unexpectedly or the message misreported its own length'); - // Reading fields with the wrong wire type should assert. - reader = jspb.BinaryReader.alloc([9, 0, 0, 0, 0, 0, 0, 0, 0]); - reader.nextField(); - expect(() => { - reader.readInt32(); - }).toThrow(); - expect(function () { - reader.readInt32String(); - }).toThrow(); - expect(function () { - reader.readInt64(); - }).toThrow(); - expect(function () { - reader.readInt64String(); - }).toThrow(); - expect(function () { - reader.readUint32(); - }).toThrow(); - expect(function () { - reader.readUint32String(); - }).toThrow(); - expect(function () { - reader.readUint64(); - }).toThrow(); - expect(function () { - reader.readUint64String(); - }).toThrow(); - expect(function () { - reader.readSint32(); - }).toThrow(); - expect(function () { - reader.readBool(); - }).toThrow(); - expect(function () { - reader.readEnum(); - }).toThrow(); - - reader = jspb.BinaryReader.alloc([8, 1]); - reader.nextField(); - expect(function () { - reader.readFixed32(); - }).toThrow(); - expect(function () { - reader.readFixed64(); - }).toThrow(); - expect(function () { - reader.readSfixed32(); - }).toThrow(); - expect(function () { - reader.readSfixed64(); - }).toThrow(); - expect(function () { - reader.readFloat(); - }).toThrow(); - expect(function () { - reader.readDouble(); - }).toThrow(); - - expect(function () { - reader.readString(); - }).toThrow(); - expect(function () { - reader.readBytes(); - }).toThrow(); - }); + // Skipping an invalid field should trigger an assertion. + reader = BinaryReader.alloc([12, 1]); + reader.nextWireType_ = 1000; + expect(() => reader.skipField()).toThrowError(); + // Reading fields with the wrong wire type should assert. + reader = BinaryReader.alloc([9, 0, 0, 0, 0, 0, 0, 0, 0]); + reader.nextField(); + expect(() => reader.readInt32()).toThrowError(); + expect(() => reader.readInt32String()).toThrowError(); + expect(() => reader.readInt64()).toThrowError(); + expect(() => reader.readInt64String()).toThrowError(); + expect(() => reader.readUint32()).toThrowError(); + expect(() => reader.readUint32String()).toThrowError(); + expect(() => reader.readUint64()).toThrowError(); + expect(() => reader.readUint64String()).toThrowError(); + expect(() => reader.readSint32()).toThrowError(); + expect(() => reader.readBool()).toThrowError(); + expect(() => reader.readEnum()).toThrowError(); + + reader = BinaryReader.alloc([8, 1]); + reader.nextField(); + expect(() => reader.readFixed32()).toThrowError(); + expect(() => reader.readFixed64()).toThrowError(); + expect(() => reader.readSfixed32()).toThrowError(); + expect(() => reader.readSfixed64()).toThrowError(); + expect(() => reader.readFloat()).toThrowError(); + expect(() => reader.readDouble()).toThrowError(); + + expect(() => reader.readString()).toThrowError(); + expect(() => reader.readBytes()).toThrowError(); + }); /** * Tests encoding and decoding of unsigned field types. - * @param {Function} readField - * @param {Function} writeField + * @param {!Function} readField + * @param {!Function} writeField * @param {number} epsilon * @param {number} upperLimit - * @param {Function} filter - * @private - * @suppress {missingProperties} + * @param {!Function} filter + * @private @suppress {missingProperties} */ - const doTestUnsignedField_ = function( - readField, writeField, epsilon, upperLimit, filter) { - expect(readField).not.toBeNull(); - expect(writeField).not.toBeNull(); + const doTestUnsignedField = + (readField, writeField, epsilon, upperLimit, filter) => { + expect(readField).not.toBeNull(); + expect(writeField).not.toBeNull(); - const writer = new jspb.BinaryWriter(); + const writer = new BinaryWriter(); - // Encode zero and limits. - writeField.call(writer, 1, filter(0)); - writeField.call(writer, 2, filter(epsilon)); - writeField.call(writer, 3, filter(upperLimit)); + // Encode zero and limits. + writeField.call(writer, 1, filter(0)); + writeField.call(writer, 2, filter(epsilon)); + writeField.call(writer, 3, filter(upperLimit)); - // Encode positive values. - for (let cursor = epsilon; cursor < upperLimit; cursor *= 1.1) { - writeField.call(writer, 4, filter(cursor)); - } - - const reader = jspb.BinaryReader.alloc(writer.getResultBuffer()); + // Encode positive values. + for (let cursor = epsilon; cursor < upperLimit; cursor *= 1.1) { + writeField.call(writer, 4, filter(cursor)); + } - // Check zero and limits. - reader.nextField(); - expect(reader.getFieldNumber()).toEqual(1); - expect(readField.call(reader)).toEqual(filter(0)); + const reader = BinaryReader.alloc(writer.getResultBuffer()); - reader.nextField(); - expect(reader.getFieldNumber()).toEqual(2); - expect(readField.call(reader)).toEqual(filter(epsilon)); + // Check zero and limits. + reader.nextField(); + expect(reader.getFieldNumber()).toEqual(1); + expect(readField.call(reader)).toEqual(filter(0)); - reader.nextField(); - expect(reader.getFieldNumber()).toEqual(3); - expect(readField.call(reader)).toEqual(filter(upperLimit)); + reader.nextField(); + expect(reader.getFieldNumber()).toEqual(2); + expect(readField.call(reader)).toEqual(filter(epsilon)); - // Check positive values. - for (let cursor = epsilon; cursor < upperLimit; cursor *= 1.1) { reader.nextField(); - if (4 != reader.getFieldNumber()) throw 'fail!'; - if (filter(cursor) != readField.call(reader)) throw 'fail!'; - } - }; + expect(reader.getFieldNumber()).toEqual(3); + expect(readField.call(reader)).toEqual(filter(upperLimit)); + // Check positive values. + for (let cursor = epsilon; cursor < upperLimit; cursor *= 1.1) { + reader.nextField(); + if (4 != reader.getFieldNumber()) throw 'fail!'; + if (filter(cursor) != readField.call(reader)) throw 'fail!'; + } + }; /** * Tests encoding and decoding of signed field types. - * @param {Function} readField - * @param {Function} writeField + * @param {!Function} readField + * @param {!Function} writeField * @param {number} epsilon * @param {number} lowerLimit * @param {number} upperLimit - * @param {Function} filter - * @private - * @suppress {missingProperties} + * @param {!Function} filter + * @private @suppress {missingProperties} */ - const doTestSignedField_ = function( - readField, writeField, epsilon, lowerLimit, upperLimit, filter) { - const writer = new jspb.BinaryWriter(); - - // Encode zero and limits. - writeField.call(writer, 1, filter(lowerLimit)); - writeField.call(writer, 2, filter(-epsilon)); - writeField.call(writer, 3, filter(0)); - writeField.call(writer, 4, filter(epsilon)); - writeField.call(writer, 5, filter(upperLimit)); - - const inputValues = []; - - // Encode negative values. - for (let cursor = lowerLimit; cursor < -epsilon; cursor /= 1.1) { - const val = filter(cursor); - writeField.call(writer, 6, val); - inputValues.push({fieldNumber: 6, value: val}); - } - - // Encode positive values. - for (let cursor = epsilon; cursor < upperLimit; cursor *= 1.1) { - const val = filter(cursor); - writeField.call(writer, 7, val); - inputValues.push({fieldNumber: 7, value: val}); - } + const doTestSignedField = + (readField, writeField, epsilon, lowerLimit, upperLimit, filter) => { + const writer = new BinaryWriter(); + + // Encode zero and limits. + writeField.call(writer, 1, filter(lowerLimit)); + writeField.call(writer, 2, filter(-epsilon)); + writeField.call(writer, 3, filter(0)); + writeField.call(writer, 4, filter(epsilon)); + writeField.call(writer, 5, filter(upperLimit)); + + const inputValues = []; + + // Encode negative values. + for (let cursor = lowerLimit; cursor < -epsilon; cursor /= 1.1) { + const val = filter(cursor); + writeField.call(writer, 6, val); + inputValues.push({ + fieldNumber: 6, + value: val, + }); + } - const reader = jspb.BinaryReader.alloc(writer.getResultBuffer()); + // Encode positive values. + for (let cursor = epsilon; cursor < upperLimit; cursor *= 1.1) { + const val = filter(cursor); + writeField.call(writer, 7, val); + inputValues.push({ + fieldNumber: 7, + value: val, + }); + } - // Check zero and limits. - reader.nextField(); - expect(reader.getFieldNumber()).toEqual(1); - expect(readField.call(reader)).toEqual(filter(lowerLimit)); + const reader = BinaryReader.alloc(writer.getResultBuffer()); - reader.nextField(); - expect(reader.getFieldNumber()).toEqual(2); - expect(readField.call(reader)).toEqual(filter(-epsilon)); + // Check zero and limits. + reader.nextField(); + expect(reader.getFieldNumber()).toEqual(1); + expect(readField.call(reader)).toEqual(filter(lowerLimit)); - reader.nextField(); - expect(reader.getFieldNumber()).toEqual(3); - expect(readField.call(reader)).toEqual(filter(0)); + reader.nextField(); + expect(reader.getFieldNumber()).toEqual(2); + expect(readField.call(reader)).toEqual(filter(-epsilon)); - reader.nextField(); - expect(reader.getFieldNumber()).toEqual(4); - expect(readField.call(reader)).toEqual(filter(epsilon)); + reader.nextField(); + expect(reader.getFieldNumber()).toEqual(3); + expect(readField.call(reader)).toEqual(filter(0)); - reader.nextField(); - expect(reader.getFieldNumber()).toEqual(5); - expect(readField.call(reader)).toEqual(filter(upperLimit)); + reader.nextField(); + expect(reader.getFieldNumber()).toEqual(4); + expect(readField.call(reader)).toEqual(filter(epsilon)); - for (let i = 0; i < inputValues.length; i++) { - const expected = inputValues[i]; reader.nextField(); - expect(reader.getFieldNumber()).toEqual(expected.fieldNumber); - expect(readField.call(reader)).toEqual(expected.value); - } - }; + expect(reader.getFieldNumber()).toEqual(5); + expect(readField.call(reader)).toEqual(filter(upperLimit)); + for (let i = 0; i < inputValues.length; i++) { + const expected = inputValues[i]; + reader.nextField(); + expect(reader.getFieldNumber()).toEqual(expected.fieldNumber); + expect(readField.call(reader)).toEqual(expected.value); + } + }; - /** - * Tests fields that use varint encoding. - */ + /** Tests fields that use varint encoding. */ it('testVarintFields', () => { - expect(jspb.BinaryReader.prototype.readUint32).not.toBeUndefined(); - expect(jspb.BinaryWriter.prototype.writeUint32).not.toBeUndefined(); - expect(jspb.BinaryReader.prototype.readUint64).not.toBeUndefined(); - expect(jspb.BinaryWriter.prototype.writeUint64).not.toBeUndefined(); - expect(jspb.BinaryReader.prototype.readBool).not.toBeUndefined(); - expect(jspb.BinaryWriter.prototype.writeBool).not.toBeUndefined(); - doTestUnsignedField_( - jspb.BinaryReader.prototype.readUint32, - jspb.BinaryWriter.prototype.writeUint32, 1, Math.pow(2, 32) - 1, - Math.round); - - doTestUnsignedField_( - jspb.BinaryReader.prototype.readUint64, - jspb.BinaryWriter.prototype.writeUint64, 1, Math.pow(2, 64) - 1025, - Math.round); - - doTestSignedField_( - jspb.BinaryReader.prototype.readInt32, - jspb.BinaryWriter.prototype.writeInt32, 1, -Math.pow(2, 31), - Math.pow(2, 31) - 1, Math.round); - - doTestSignedField_( - jspb.BinaryReader.prototype.readInt64, - jspb.BinaryWriter.prototype.writeInt64, 1, -Math.pow(2, 63), - Math.pow(2, 63) - 513, Math.round); - - doTestSignedField_( - jspb.BinaryReader.prototype.readEnum, - jspb.BinaryWriter.prototype.writeEnum, 1, -Math.pow(2, 31), - Math.pow(2, 31) - 1, Math.round); - - doTestUnsignedField_( - jspb.BinaryReader.prototype.readBool, - jspb.BinaryWriter.prototype.writeBool, 1, 1, function(x) { - return !!x; - }); + expect(BinaryReader.prototype.readUint32).toBeDefined(); + expect(BinaryWriter.prototype.writeUint32).toBeDefined(); + expect(BinaryReader.prototype.readUint64).toBeDefined(); + expect(BinaryWriter.prototype.writeUint64).toBeDefined(); + expect(BinaryReader.prototype.readBool).toBeDefined(); + expect(BinaryWriter.prototype.writeBool).toBeDefined(); + doTestUnsignedField( + BinaryReader.prototype.readUint32, BinaryWriter.prototype.writeUint32, + 1, Math.pow(2, 32) - 1, Math.round); + + doTestUnsigned64BitIntField( + BinaryReader.prototype.readUint64, BinaryWriter.prototype.writeUint64); + + doTestSignedField( + BinaryReader.prototype.readInt32, BinaryWriter.prototype.writeInt32, 1, + -Math.pow(2, 31), Math.pow(2, 31) - 1, Math.round); + + doTestSigned64BitIntField(BinaryReader.prototype.readInt64, BinaryWriter.prototype.writeInt64); + + doTestSignedField( + BinaryReader.prototype.readEnum, BinaryWriter.prototype.writeEnum, 1, + -Math.pow(2, 31), Math.pow(2, 31) - 1, Math.round); + + doTestUnsignedField( + BinaryReader.prototype.readBool, BinaryWriter.prototype.writeBool, 1, 1, + (x) => !!x); }); - /** * Tests reading a field from hexadecimal string (format: '08 BE EF'). - * @param {Function} readField + * @param {!Function} readField * @param {number} expected * @param {string} hexString */ - function doTestHexStringVarint_(readField, expected, hexString) { + function doTestHexStringVarint(readField, expected, hexString) { const bytesCount = (hexString.length + 1) / 3; const bytes = new Uint8Array(bytesCount); for (let i = 0; i < bytesCount; i++) { bytes[i] = parseInt(hexString.substring(i * 3, i * 3 + 2), 16); } - const reader = jspb.BinaryReader.alloc(bytes); + const reader = BinaryReader.alloc(bytes); reader.nextField(); expect(readField.call(reader)).toEqual(expected); } - - /** - * Tests non-canonical redundant varint decoding. - */ + /** Tests non-canonical redundant varint decoding. */ it('testRedundantVarintFields', () => { - expect(jspb.BinaryReader.prototype.readUint32).not.toBeNull(); - expect(jspb.BinaryReader.prototype.readUint64).not.toBeNull(); - expect(jspb.BinaryReader.prototype.readSint32).not.toBeNull(); - expect(jspb.BinaryReader.prototype.readSint64).not.toBeNull(); + expect(BinaryReader.prototype.readUint32).toBeDefined(); + expect(BinaryReader.prototype.readUint64).toBeDefined(); + expect(BinaryReader.prototype.readSint32).toBeDefined(); + expect(BinaryReader.prototype.readSint64).toBeDefined(); // uint32 and sint32 take no more than 5 bytes // 08 - field prefix (type = 0 means varint) - doTestHexStringVarint_( - jspb.BinaryReader.prototype.readUint32, 12, '08 8C 80 80 80 00'); + doTestHexStringVarint( + BinaryReader.prototype.readUint32, 12, '08 8C 80 80 80 00'); // 11 stands for -6 in zigzag encoding - doTestHexStringVarint_( - jspb.BinaryReader.prototype.readSint32, -6, '08 8B 80 80 80 00'); + doTestHexStringVarint( + BinaryReader.prototype.readSint32, -6, '08 8B 80 80 80 00'); // uint64 and sint64 take no more than 10 bytes // 08 - field prefix (type = 0 means varint) - doTestHexStringVarint_( - jspb.BinaryReader.prototype.readUint64, 12, - '08 8C 80 80 80 80 80 80 80 80 00'); + doTestHexStringVarint( + BinaryReader.prototype.readUint64, 12, + '08 8C 80 80 80 80 80 80 80 80 00'); // 11 stands for -6 in zigzag encoding - doTestHexStringVarint_( - jspb.BinaryReader.prototype.readSint64, -6, - '08 8B 80 80 80 80 80 80 80 80 00'); + doTestHexStringVarint( + BinaryReader.prototype.readSint64, -6, + '08 8B 80 80 80 80 80 80 80 80 00'); }); - /** - * Tests reading 64-bit integers as split values. - */ + /** Tests reading 64-bit integers as split values. */ it('handles split 64 fields', () => { - const writer = new jspb.BinaryWriter(); + const writer = new BinaryWriter(); writer.writeInt64String(1, '4294967296'); writer.writeSfixed64String(2, '4294967298'); writer.writeInt64String(3, '3'); // 3 is the zig-zag encoding of -2. - const reader = jspb.BinaryReader.alloc(writer.getResultBuffer()); + const reader = BinaryReader.alloc(writer.getResultBuffer()); function rejoin(lowBits, highBits) { return highBits * 2 ** 32 + (lowBits >>> 0); @@ -471,23 +523,33 @@ describe('binaryReaderTest', () => { expect(reader.readSplitZigzagVarint64(rejoin)).toEqual(-2); }); - /** - * Tests 64-bit fields that are handled as strings. - */ + /** Tests 64-bit fields that are handled as strings. */ it('testStringInt64Fields', () => { - const writer = new jspb.BinaryWriter(); + const writer = new BinaryWriter(); const testSignedData = [ - '2730538252207801776', '-2688470994844604560', '3398529779486536359', - '3568577411627971000', '272477188847484900', '-6649058714086158188', - '-7695254765712060806', '-4525541438037104029', '-4993706538836508568', - '4990160321893729138' + '2730538252207801776', + '-2688470994844604560', + '3398529779486536359', + '3568577411627971000', + '272477188847484900', + '-6649058714086158188', + '-7695254765712060806', + '-4525541438037104029', + '-4993706538836508568', + '4990160321893729138', ]; const testUnsignedData = [ - '7822732630241694882', '6753602971916687352', '2399935075244442116', - '8724292567325338867', '16948784802625696584', '4136275908516066934', - '3575388346793700364', '5167142028379259461', '1557573948689737699', - '17100725280812548567' + '7822732630241694882', + '6753602971916687352', + '2399935075244442116', + '8724292567325338867', + '16948784802625696584', + '4136275908516066934', + '3575388346793700364', + '5167142028379259461', + '1557573948689737699', + '17100725280812548567', ]; for (let i = 0; i < testSignedData.length; i++) { @@ -495,100 +557,67 @@ describe('binaryReaderTest', () => { writer.writeUint64String(2 * i + 2, testUnsignedData[i]); } - const reader = jspb.BinaryReader.alloc(writer.getResultBuffer()); + const reader = BinaryReader.alloc(writer.getResultBuffer()); for (let i = 0; i < testSignedData.length; i++) { reader.nextField(); expect(reader.getFieldNumber()).toEqual(2 * i + 1); - expect(testSignedData[i]).toEqual(reader.readInt64String()); + expect(reader.readInt64String()).toEqual(testSignedData[i]); reader.nextField(); expect(reader.getFieldNumber()).toEqual(2 * i + 2); - expect(testUnsignedData[i]).toEqual(reader.readUint64String()); + expect(reader.readUint64String()).toEqual(testUnsignedData[i]); } }); - - /** - * Tests fields that use zigzag encoding. - */ + /** Tests fields that use zigzag encoding. */ it('testZigzagFields', () => { - doTestSignedField_( - jspb.BinaryReader.prototype.readSint32, - jspb.BinaryWriter.prototype.writeSint32, 1, -Math.pow(2, 31), - Math.pow(2, 31) - 1, Math.round); - - doTestSignedField_( - 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); - }); + doTestSignedField( + BinaryReader.prototype.readSint32, BinaryWriter.prototype.writeSint32, + 1, -Math.pow(2, 31), Math.pow(2, 31) - 1, Math.round); + doTestSigned64BitIntField(BinaryReader.prototype.readSint64, BinaryWriter.prototype.writeSint64); + }); - /** - * Tests fields that use fixed-length encoding. - */ + /** Tests fields that use fixed-length encoding. */ it('testFixedFields', () => { - doTestUnsignedField_( - jspb.BinaryReader.prototype.readFixed32, - jspb.BinaryWriter.prototype.writeFixed32, 1, Math.pow(2, 32) - 1, - Math.round); - - doTestUnsignedField_( - jspb.BinaryReader.prototype.readFixed64, - jspb.BinaryWriter.prototype.writeFixed64, 1, Math.pow(2, 64) - 1025, - Math.round); - - doTestSignedField_( - jspb.BinaryReader.prototype.readSfixed32, - jspb.BinaryWriter.prototype.writeSfixed32, 1, -Math.pow(2, 31), - Math.pow(2, 31) - 1, Math.round); - - doTestSignedField_( - jspb.BinaryReader.prototype.readSfixed64, - jspb.BinaryWriter.prototype.writeSfixed64, 1, -Math.pow(2, 63), - Math.pow(2, 63) - 513, Math.round); - }); + doTestUnsignedField( + BinaryReader.prototype.readFixed32, BinaryWriter.prototype.writeFixed32, + 1, Math.pow(2, 32) - 1, Math.round); + doTestUnsigned64BitIntField(BinaryReader.prototype.readFixed64, BinaryWriter.prototype.writeFixed64); - /** - * Tests floating point fields. - */ - it('testFloatFields', () => { - doTestSignedField_( - jspb.BinaryReader.prototype.readFloat, - jspb.BinaryWriter.prototype.writeFloat, - jspb.BinaryConstants.FLOAT32_MIN, -jspb.BinaryConstants.FLOAT32_MAX, - jspb.BinaryConstants.FLOAT32_MAX, truncate); - - doTestSignedField_( - jspb.BinaryReader.prototype.readDouble, - jspb.BinaryWriter.prototype.writeDouble, - jspb.BinaryConstants.FLOAT64_EPS * 10, - -jspb.BinaryConstants.FLOAT64_MIN, jspb.BinaryConstants.FLOAT64_MIN, - function(x) { - return x; - }); + doTestSignedField( + BinaryReader.prototype.readSfixed32, + BinaryWriter.prototype.writeSfixed32, 1, -Math.pow(2, 31), + Math.pow(2, 31) - 1, Math.round); + + doTestSigned64BitIntField(BinaryReader.prototype.readSfixed64, BinaryWriter.prototype.writeSfixed64); }); + /** Tests floating point fields. */ + it('testFloatFields', () => { + doTestSignedField( + BinaryReader.prototype.readFloat, BinaryWriter.prototype.writeFloat, + BinaryConstants.FLOAT32_MIN, -BinaryConstants.FLOAT32_MAX, + BinaryConstants.FLOAT32_MAX, truncate); + + doTestSignedField( + BinaryReader.prototype.readDouble, BinaryWriter.prototype.writeDouble, + BinaryConstants.FLOAT64_EPS * 10, -BinaryConstants.FLOAT64_MIN, + BinaryConstants.FLOAT64_MIN, (x) => x); + }); - /** - * Tests length-delimited string fields. - */ + /** Tests length-delimited string fields. */ it('testStringFields', () => { const s1 = 'The quick brown fox jumps over the lazy dog.'; const s2 = '人人生而自由,在尊嚴和權利上一律平等。'; - const writer = new jspb.BinaryWriter(); + const writer = new BinaryWriter(); writer.writeString(1, s1); writer.writeString(2, s2); - const reader = jspb.BinaryReader.alloc(writer.getResultBuffer()); + const reader = BinaryReader.alloc(writer.getResultBuffer()); reader.nextField(); expect(reader.getFieldNumber()).toEqual(1); @@ -599,17 +628,12 @@ describe('binaryReaderTest', () => { expect(reader.readString()).toEqual(s2); }); - - /** - * Tests length-delimited byte fields. - */ + /** Tests length-delimited byte fields. */ it('testByteFields', () => { - const message = []; const lowerLimit = 1; const upperLimit = 256; - const scale = 1.1; - const writer = new jspb.BinaryWriter(); + const writer = new BinaryWriter(); for (let cursor = lowerLimit; cursor < upperLimit; cursor *= 1.1) { const len = Math.round(cursor); @@ -619,7 +643,7 @@ describe('binaryReaderTest', () => { writer.writeBytes(len, bytes); } - const reader = jspb.BinaryReader.alloc(writer.getResultBuffer()); + const reader = BinaryReader.alloc(writer.getResultBuffer()); for (let cursor = lowerLimit; reader.nextField(); cursor *= 1.1) { const len = Math.round(cursor); @@ -631,15 +655,13 @@ describe('binaryReaderTest', () => { if (i % 256 != bytes[i]) throw 'fail!'; } } + expect().nothing(); // suppress 'no expectations' warning }); - - /** - * Tests nested messages. - */ + /** Tests nested messages. */ it('testNesting', () => { - const writer = new jspb.BinaryWriter(); - const dummyMessage = /** @type {!jspb.BinaryMessage} */ ({}); + const writer = new BinaryWriter(); + const dummyMessage = /** @type {!BinaryMessage} */ ({}); writer.writeInt32(1, 100); @@ -651,17 +673,17 @@ describe('binaryReaderTest', () => { }); // Add one empty message. - writer.writeMessage(6, dummyMessage, () => {}); + writer.writeMessage(6, dummyMessage, () => { }); writer.writeInt32(7, 700); - const reader = jspb.BinaryReader.alloc(writer.getResultBuffer()); + const reader = BinaryReader.alloc(writer.getResultBuffer()); // Validate outermost message. reader.nextField(); expect(reader.getFieldNumber()).toEqual(1); - expect(100).toEqual(reader.readInt32()); + expect(reader.readInt32()).toEqual(100); reader.nextField(); expect(reader.getFieldNumber()).toEqual(2); @@ -669,15 +691,15 @@ describe('binaryReaderTest', () => { // Validate embedded message 1. reader.nextField(); expect(reader.getFieldNumber()).toEqual(3); - expect(300).toEqual(reader.readInt32()); + expect(reader.readInt32()).toEqual(300); reader.nextField(); expect(reader.getFieldNumber()).toEqual(4); - expect(400).toEqual(reader.readInt32()); + expect(reader.readInt32()).toEqual(400); reader.nextField(); expect(reader.getFieldNumber()).toEqual(5); - expect(500).toEqual(reader.readInt32()); + expect(reader.readInt32()).toEqual(500); expect(reader.nextField()).toEqual(false); }); @@ -692,7 +714,7 @@ describe('binaryReaderTest', () => { reader.nextField(); expect(reader.getFieldNumber()).toEqual(7); - expect(700).toEqual(reader.readInt32()); + expect(reader.readInt32()).toEqual(700); expect(reader.nextField()).toEqual(false); }); @@ -702,7 +724,7 @@ describe('binaryReaderTest', () => { * values and skipping everything that's not a sentinel. */ it('testSkipField', () => { - const writer = new jspb.BinaryWriter(); + const writer = new BinaryWriter(); const sentinel = 123456789; @@ -732,7 +754,7 @@ describe('binaryReaderTest', () => { // Write a group with a nested group inside. writer.writeInt32(5, sentinel); - const dummyMessage = /** @type {!jspb.BinaryMessage} */ ({}); + const dummyMessage = /** @type {!BinaryMessage} */ ({}); writer.writeGroup(5, dummyMessage, () => { // Previously the skipGroup implementation was wrong, which only consume // the decoder by nextField. This case is for making the previous @@ -751,18 +773,18 @@ describe('binaryReaderTest', () => { writer.writeInt64(84, 42); writer.writeInt64(84, 44); writer.writeBytes( - 43, [255, 255, 255, 255, 255, 255, 255, 255, 255, 255]); + 43, [255, 255, 255, 255, 255, 255, 255, 255, 255, 255]); }); }); // Write final sentinel. writer.writeInt32(6, sentinel); - const reader = jspb.BinaryReader.alloc(writer.getResultBuffer()); + const reader = BinaryReader.alloc(writer.getResultBuffer()); function skip(field, count) { for (let i = 0; i < count; i++) { - reader.nextField(); + expect(reader.nextField()).toBe(true); if (field != reader.getFieldNumber()) throw 'fail!'; reader.skipField(); } @@ -770,40 +792,37 @@ describe('binaryReaderTest', () => { reader.nextField(); expect(reader.getFieldNumber()).toEqual(1); - expect(sentinel).toEqual(reader.readInt32()); + expect(reader.readInt32()).toEqual(sentinel); skip(1, 4); reader.nextField(); expect(reader.getFieldNumber()).toEqual(2); - expect(sentinel).toEqual(reader.readInt32()); + expect(reader.readInt32()).toEqual(sentinel); skip(2, 3); reader.nextField(); expect(reader.getFieldNumber()).toEqual(3); - expect(sentinel).toEqual(reader.readInt32()); + expect(reader.readInt32()).toEqual(sentinel); skip(3, 3); reader.nextField(); expect(reader.getFieldNumber()).toEqual(4); - expect(sentinel).toEqual(reader.readInt32()); + expect(reader.readInt32()).toEqual(sentinel); skip(4, 2); reader.nextField(); expect(reader.getFieldNumber()).toEqual(5); - expect(sentinel).toEqual(reader.readInt32()); + expect(reader.readInt32()).toEqual(sentinel); skip(5, 1); reader.nextField(); expect(reader.getFieldNumber()).toEqual(6); - expect(sentinel).toEqual(reader.readInt32()); + expect(reader.readInt32()).toEqual(sentinel); }); - - /** - * Tests packed fields. - */ - it('testPackedFields', () => { - const writer = new jspb.BinaryWriter(); + /** Tests Packable fields. */ + it('testPackableFields', () => { + const writer = new BinaryWriter(); const sentinel = 123456789; @@ -836,58 +855,85 @@ describe('binaryReaderTest', () => { writer.writeInt32(3, sentinel); - const reader = jspb.BinaryReader.alloc(writer.getResultBuffer()); + const reader = BinaryReader.alloc(writer.getResultBuffer()); reader.nextField(); - expect(sentinel).toEqual(reader.readInt32()); + expect(reader.readInt32()).toEqual(sentinel); reader.nextField(); - expect(signedData).toEqual(reader.readPackedInt32()); + const array = []; + reader.readPackableInt32Into(array); + expect(array).toEqual(signedData); reader.nextField(); - expect(signedData).toEqual(reader.readPackedInt64()); + array.length = 0; + reader.readPackableInt64Into(array); + expect(array).toEqual(signedData); reader.nextField(); - expect(unsignedData).toEqual(reader.readPackedUint32()); + array.length = 0; + reader.readPackableUint32Into(array); + expect(array).toEqual(unsignedData); reader.nextField(); - expect(unsignedData).toEqual(reader.readPackedUint64()); + array.length = 0; + reader.readPackableUint64Into(array); + expect(array).toEqual(unsignedData); reader.nextField(); - expect(signedData).toEqual(reader.readPackedSint32()); + array.length = 0; + reader.readPackableSint32Into(array); + expect(array).toEqual(signedData); reader.nextField(); - expect(signedData).toEqual(reader.readPackedSint64()); + array.length = 0; + reader.readPackableSint64Into(array); + expect(array).toEqual(signedData); reader.nextField(); - expect(unsignedData).toEqual(reader.readPackedFixed32()); + array.length = 0; + reader.readPackableFixed32Into(array); + expect(array).toEqual(unsignedData); reader.nextField(); - expect(unsignedData).toEqual(reader.readPackedFixed64()); + array.length = 0; + reader.readPackableFixed64Into(array); + expect(array).toEqual(unsignedData); reader.nextField(); - expect(signedData).toEqual(reader.readPackedSfixed32()); + array.length = 0; + reader.readPackableSfixed32Into(array); + expect(array).toEqual(signedData); reader.nextField(); - expect(signedData).toEqual(reader.readPackedSfixed64()); + array.length = 0; + reader.readPackableSfixed64Into(array); + expect(array).toEqual(signedData); reader.nextField(); - expect(floatData).toEqual(reader.readPackedFloat()); + array.length = 0; + reader.readPackableFloatInto(array); + expect(array).toEqual(floatData); reader.nextField(); - expect(doubleData).toEqual(reader.readPackedDouble()); + array.length = 0; + reader.readPackableDoubleInto(array); + expect(array).toEqual(doubleData); reader.nextField(); - expect(boolData).toEqual(reader.readPackedBool()); + array.length = 0; + reader.readPackableBoolInto(array); + expect(array).toEqual(boolData); reader.nextField(); - expect(unsignedData).toEqual(reader.readPackedEnum()); + array.length = 0; + reader.readPackableEnumInto(array); + expect(array).toEqual(unsignedData); reader.nextField(); - expect(sentinel).toEqual(reader.readInt32()); + expect(reader.readInt32()).toEqual(sentinel); }); - /** * Byte blobs inside nested messages should always have their byte offset set * relative to the start of the outermost blob, not the start of their parent @@ -897,10 +943,10 @@ describe('binaryReaderTest', () => { // Create a proto consisting of two nested messages, with the inner one // containing a blob of bytes. - const fieldTag = (1 << 3) | /* jspb.BinaryConstants.WireType.DELIMITED = */ 2; + const fieldTag = (1 << 3) | BinaryConstants.WireType.DELIMITED; const blob = [1, 2, 3, 4, 5]; - const writer = new jspb.BinaryWriter(); - const dummyMessage = /** @type {!jspb.BinaryMessage} */ ({}); + const writer = new BinaryWriter(); + const dummyMessage = /** @type {!BinaryMessage} */ ({}); writer.writeMessage(1, dummyMessage, () => { writer.writeMessage(1, dummyMessage, () => { @@ -911,30 +957,33 @@ describe('binaryReaderTest', () => { // Peel off the outer two message layers. Each layer should have two bytes // of overhead, one for the field tag and one for the length of the inner // blob. - - const decoder1 = new jspb.BinaryDecoder(writer.getResultBuffer()); - expect(fieldTag).toEqual(decoder1.readUnsignedVarint32()); - expect(blob.length + 4).toEqual(decoder1.readUnsignedVarint32()); - - const decoder2 = - new jspb.BinaryDecoder(decoder1.readBytes(blob.length + 4)); - expect(fieldTag).toEqual(decoder2.readUnsignedVarint32()); - expect(blob.length + 2).toEqual(decoder2.readUnsignedVarint32()); - - expect(fieldTag).toEqual(decoder2.readUnsignedVarint32()); - expect(blob.length).toEqual(decoder2.readUnsignedVarint32()); + const buf = writer.getResultBuffer(); + const decoder1 = new BinaryDecoder(buf); + expect(BinaryDecoder.readUnsignedVarint32(decoder1)).toEqual(fieldTag); + expect(BinaryDecoder.readUnsignedVarint32(decoder1)) + .toEqual(blob.length + 4); + + const decoder2 = new BinaryDecoder(decoder1.readBytes(blob.length + 4)); + expect(BinaryDecoder.readUnsignedVarint32(decoder2)).toEqual(fieldTag); + expect(BinaryDecoder.readUnsignedVarint32(decoder2)) + .toEqual(blob.length + 2); + + expect(BinaryDecoder.readUnsignedVarint32(decoder2)).toEqual(fieldTag); + expect(BinaryDecoder.readUnsignedVarint32(decoder2)).toEqual(blob.length); const bytes = decoder2.readBytes(blob.length); - expect(Uint8Array.from(blob)).toEqual(bytes); + // Mutating the input buffer shouldn't matter here because it will have been + // copied. + buf.fill(0); + + // toEqual doesn't support uint8array, so convert to arrays. + expect(Array.from(bytes)).toEqual(Array.from(blob)); }); - - /** - * Tests read callbacks. - */ - it('testReadCallbacks', () => { - const writer = new jspb.BinaryWriter(); - const dummyMessage = /** @type {!jspb.BinaryMessage} */ ({}); + /** Tests read callbacks. */ + it('test read message', () => { + const writer = new BinaryWriter(); + const dummyMessage = /** @type {!BinaryMessage} */ ({}); // Add an int, a submessage, and another int. writer.writeInt32(1, 100); @@ -947,47 +996,125 @@ describe('binaryReaderTest', () => { writer.writeInt32(7, 700); - // Create the reader and register a custom read callback. - const reader = jspb.BinaryReader.alloc(writer.getResultBuffer()); + const reader = BinaryReader.alloc(writer.getResultBuffer()); + + // Read the container message. + reader.nextField(); + expect(reader.getFieldNumber()).toEqual(1); + expect(reader.readInt32()).toEqual(100); - /** - * @param {!jspb.BinaryReader} reader - * @return {*} - */ - function readCallback(reader) { + reader.nextField(); + expect(reader.getFieldNumber()).toEqual(2); + reader.readMessage(dummyMessage, () => { reader.nextField(); expect(reader.getFieldNumber()).toEqual(3); - expect(300).toEqual(reader.readInt32()); + expect(reader.readInt32()).toEqual(300); reader.nextField(); expect(reader.getFieldNumber()).toEqual(4); - expect(400).toEqual(reader.readInt32()); + expect(reader.readInt32()).toEqual(400); reader.nextField(); expect(reader.getFieldNumber()).toEqual(5); - expect(500).toEqual(reader.readInt32()); + expect(reader.readInt32()).toEqual(500); expect(reader.nextField()).toEqual(false); - }; - - reader.registerReadCallback('readCallback', readCallback); + }); - // Read the container message. reader.nextField(); - expect(reader.getFieldNumber()).toEqual(1); - expect(100).toEqual(reader.readInt32()); + expect(reader.getFieldNumber()).toEqual(7); + expect(reader.readInt32()).toEqual(700); + expect(reader.nextField()).toEqual(false); + }); + it('test group ends early when reading', () => { + const writer = new BinaryWriter(); + writer.writeGroup(1, {}, (msg, writer) => { + writer.writeString(1, 'hello'); + }); + const reader = BinaryReader.alloc(writer.getResultBuffer()); + // Normal reading works fine + expect(reader.nextField()).toBe(true); + reader.readGroup(1, {}, (msg, reader) => { + expect(reader.nextField()).toBe(true); + expect(reader.readString()).toBe('hello'); + expect(reader.nextField()).toBe(true); + }); + reader.reset(); + expect(reader.nextField()).toBe(true); + // If the reader callback returns early then an error will be thrown + expect(() => { + reader.readGroup(1, {}, (msg, reader) => { + expect(reader.nextField()).toBe(true); + expect(reader.readString()).toBe('hello'); + }); + }).toThrowError('Group submessage did not end with an END_GROUP tag'); + }); + it('test group ends with wrong tag when reading', () => { + const writer = new BinaryWriter(); + writer.writeGroup(1, {}, (msg, writer) => { + writer.writeString(1, 'hello'); + }); + const bytes = writer.getResultBuffer(); + // make the end tag at the end correspond to the wrong field number + bytes[bytes.length - 1] = makeTag(2, BinaryConstants.WireType.END_GROUP); + // slice the endgroup tag off + const reader = BinaryReader.alloc(bytes); reader.nextField(); - expect(reader.getFieldNumber()).toEqual(2); - reader.readMessage(dummyMessage, () => { - // Decode the embedded message using the registered callback. - reader.runReadCallback('readCallback'); + expect(() => reader.readGroup(1, {}, (msg, reader) => { + expect(reader.nextField()).toBe(true); + expect(reader.readString()).toBe('hello'); + expect(reader.nextField()).toBe(true); + })).toThrowError('Unmatched end-group tag'); + }); + it('test group ends early when skipping', () => { + const writer = new BinaryWriter(); + writer.writeGroup(1, {}, (msg, writer) => { + writer.writeString(1, 'hello'); + }); + // slice the endgroup tag off + const reader = + BinaryReader.alloc(sliceUint8Array(writer.getResultBuffer(), 0, -1)); + reader.nextField(); + expect(() => reader.skipField()) + .toThrowError('Unmatched start-group tag: stream EOF'); + }); + + it('test group ends with wrong end tag when skipping', () => { + const writer = new BinaryWriter(); + writer.writeGroup(1, {}, (msg, writer) => { + writer.writeString(1, 'hello'); }); + const bytes = writer.getResultBuffer(); + // make the end tag at the end correspond to the wrong field number + bytes[bytes.length - 1] = makeTag(2, BinaryConstants.WireType.END_GROUP); + const reader = BinaryReader.alloc(bytes); + reader.nextField(); + expect(() => reader.skipField()).toThrowError('Unmatched end-group tag'); + }); + + it('throws when stop group appears first', () => { + const misplacedEndGroupEncoder = new BinaryEncoder(); + const fieldNumber = 1; + misplacedEndGroupEncoder.writeUnsignedVarint32( + (fieldNumber << 3) + BinaryConstants.WireType.END_GROUP); + const misplacedEndGroupData = misplacedEndGroupEncoder.end(); + const reader = BinaryReader.alloc(misplacedEndGroupData); reader.nextField(); - expect(reader.getFieldNumber()).toEqual(7); - expect(700).toEqual(reader.readInt32()); - expect(reader.nextField()).toEqual(false); + expect(() => reader.skipField()) + .toThrowError('Invalid wire type: 4 (at position 0)'); + }); + + it('throws on invalid wire types', () => { + const badWireTypeEncoder = new BinaryEncoder(); + const fieldNumber = 1; + badWireTypeEncoder.writeUnsignedVarint32( + (fieldNumber << 3) + /* max wiretype + 1 */6); + const badWireTypeData = badWireTypeEncoder.end(); + const reader = BinaryReader.alloc(badWireTypeData); + expect(() => reader.nextField()) + .toThrowError('Invalid wire type: 6 (at position 0)'); }); }); diff --git a/binary/repeated_field_type.js b/binary/repeated_field_type.js new file mode 100755 index 0000000..d6fc561 --- /dev/null +++ b/binary/repeated_field_type.js @@ -0,0 +1,11 @@ +goog.module('jspb.binary.repeated_field_type'); + +const {ScalarFieldType} = goog.requireType('jspb.binary.scalar_field_type'); + +/** + * A repeated field is an array of scalars, blobs, or messages. + * @typedef {!Array|!Array} + */ +let RepeatedFieldType; + +exports = {RepeatedFieldType}; diff --git a/binary/scalar_field_type.js b/binary/scalar_field_type.js new file mode 100755 index 0000000..c7cadb4 --- /dev/null +++ b/binary/scalar_field_type.js @@ -0,0 +1,9 @@ +goog.module('jspb.binary.scalar_field_type'); + +/** + * A scalar field can be a boolean, number, or string. + * @typedef {boolean|number|string} + */ +let ScalarFieldType; + +exports = {ScalarFieldType}; diff --git a/binary/test_utils.js b/binary/test_utils.js new file mode 100755 index 0000000..3302736 --- /dev/null +++ b/binary/test_utils.js @@ -0,0 +1,51 @@ +/** @fileoverview Utilities for testing binary protobuf code. */ +goog.module('jspb.binary.test_utils'); +goog.module.declareLegacyNamespace(); + + +const {BinaryReader} = goog.require('jspb.binary.reader'); +const {assertNumber} = goog.require('goog.asserts'); + +/** + * Converts a Uint8Array into a string of hex with two characters per byte. + * @param {!Uint8Array} u8array + * @return {string} + */ +function toHexString(u8array) { + return Array + .from( + u8array, (b) => (assertNumber(b) < 0x10 ? '0' : '') + b.toString(16)) + .join(''); +} +exports.toHexString = toHexString; + +/** + * Splits an encoded protocol buffer message into separate hex strings for each + * field number. + * + * Allows comparison of serialized protos without regard to field number + * ordering, which is not guaranteed to be stable. + * See https://developers.google.com/protocol-buffers/docs/encoding#order + * + * Contains all bytes relevant to the field, including the header with field + * number and type. Repeated fields are one long string including all headers. + * + * @param {!Uint8Array} u8array + * @return {!Object} + */ +function toHexFields(u8array) { + const /** !Object */ fields = {}; + const reader = new BinaryReader(u8array); + let startCursor = 0; + while (reader.nextField()) { + const fieldNumber = reader.getFieldNumber(); + const existingString = fields[fieldNumber] || ''; + reader.skipField(); + const endCursor = reader.getCursor(); + fields[fieldNumber] = + existingString + toHexString(u8array.subarray(startCursor, endCursor)); + startCursor = endCursor; + } + return fields; +} +exports.toHexFields = toHexFields; diff --git a/binary/utf8.js b/binary/utf8.js old mode 100644 new mode 100755 index 371982d..b25fff9 --- a/binary/utf8.js +++ b/binary/utf8.js @@ -1,26 +1,30 @@ /** * @fileoverview UTF8 encoding and decoding routines + * */ -goog.provide('jspb.binary.utf8'); +goog.module('jspb.binary.utf8'); +goog.module.declareLegacyNamespace(); -goog.require('jspb.asserts'); +const {assert, assertString} = goog.require('goog.asserts'); /** * Whether to use the browser based `TextEncoder` and `TextDecoder` APIs for * handling utf8. * - *

Enabled by default for `goog.FEATURESET_YEAR >= 2020`. The code also - * performs feature detection for this API and will always use it if available, - * this variable enables us to not ship the polyfill. - * - *

See http://go/jscompiler-flags#browser-featureset-year-options for the - * behavior here. + *

If enabled and `goog.FEATURESET_YEAR >= 2020`, `TextEncoder` and + * `TextDecoder` are used unconditionally without feature detection (and the + * code crashes if they are not available); this is best for code size. If + * enabled and `goog.FEATURESET_YEAR < 2020`, `TextEncoder` and `TextDecoder` + * are feature-detected, used if available, and the JS implementation is used + * if they're not. If disabled, the slower JS implementation is always used; + * this is appropriate for non-Web environments that don't have `TextEncoder` + * and `TextDecoder`. * * @define {boolean} */ -const USE_TEXT_ENCODING = - goog.define('jspb.binary.USE_TEXTENCODING', goog.FEATURESET_YEAR >= 2020); +const USE_TEXT_ENCODING = goog.define('jspb.binary.USE_TEXTENCODING', true); +const ASSUME_TEXT_ENCODING_AVAILABLE = goog.FEATURESET_YEAR >= 2020; const /** number */ MIN_SURROGATE = 0xD800; const /** number */ MIN_HIGH_SURROGATE = MIN_SURROGATE; @@ -75,7 +79,7 @@ function codeUnitsToString( * * @return {string} */ -jspb.binary.utf8.polyfillDecodeUtf8 = function ( +function polyfillDecodeUtf8( /** !Uint8Array */ bytes, /** number */ offset, /** number */ length, /** boolean */ parsingErrorsAreFatal) { let cursor = offset; @@ -112,7 +116,7 @@ jspb.binary.utf8.polyfillDecodeUtf8 = function ( const codeUnit = ((c1 & 0x1F) << 6) | (c2 & 0x3F); // Consistency check that the computed code is in range for a 2 byte // sequence. - jspb.asserts.assert(codeUnit >= 0x80 && codeUnit <= 0x07FF); + assert(codeUnit >= 0x80 && codeUnit <= 0x07FF); codeUnits.push(codeUnit); } } @@ -122,15 +126,15 @@ jspb.binary.utf8.polyfillDecodeUtf8 = function ( } else { c2 = bytes[cursor++]; if (isNotTrailingByte(c2) || - // These checks were taken from - // java/com/google/protobuf/Utf8.java - // overlong? 5 most significant bits must not all be zero - (c1 === 0xE0 && c2 < 0xA0) - // check for illegal surrogate codepoints - || (c1 === 0xED && c2 >= 0xA0) || - // We delay reading c3 until now so than an error in c2 or c1 will - // preserve c3 for the next loop iteration - isNotTrailingByte(c3 = bytes[cursor++])) { + // These checks were taken from + // java/com/google/protobuf/Utf8.java + // overlong? 5 most significant bits must not all be zero + (c1 === 0xE0 && c2 < 0xA0) + // check for illegal surrogate codepoints + || (c1 === 0xED && c2 >= 0xA0) || + // We delay reading c3 until now so than an error in c2 or c1 will + // preserve c3 for the next loop iteration + isNotTrailingByte(c3 = bytes[cursor++])) { cursor--; // push back c2 or c3, depending on how far we made it invalid(parsingErrorsAreFatal, codeUnits); } else { @@ -138,11 +142,11 @@ jspb.binary.utf8.polyfillDecodeUtf8 = function ( // 6 bits from each of the two lower bytes // == 16 bits total const codeUnit = - ((c1 & 0xF) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F); + ((c1 & 0xF) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F); // Consistency check, this is the valid range for a 3 byte character - jspb.asserts.assert(codeUnit >= 0x800 && codeUnit <= 0xFFFF); + assert(codeUnit >= 0x800 && codeUnit <= 0xFFFF); // And that Utf16 surrogates are disallowed - jspb.asserts.assert(codeUnit < MIN_SURROGATE || codeUnit > MAX_SURROGATE); + assert(codeUnit < MIN_SURROGATE || codeUnit > MAX_SURROGATE); codeUnits.push(codeUnit); } } @@ -154,20 +158,20 @@ jspb.binary.utf8.polyfillDecodeUtf8 = function ( } else { c2 = bytes[cursor++]; if (isNotTrailingByte(c2) || - // This check was inspired by - // java/com/google/protobuf/Utf8.java - // Tricky optimized form of: - // valid 4-byte leading byte? - // if (byte1 > (byte) 0xF4 || - // overlong? 4 most significant bits must not all be zero - // byte1 == (byte) 0xF0 && byte2 < (byte) 0x90 || - // codepoint larger than the highest code point (U+10FFFF)? - // byte1 == (byte) 0xF4 && byte2 > (byte) 0x8F) - (((c1 << 28) + (c2 - 0x90)) >> 30) !== 0 || - // We delay reading c3 and c4 until now so than an error in c2 or c1 - // will preserve them for the next loop iteration. - isNotTrailingByte(c3 = bytes[cursor++]) || - isNotTrailingByte(c4 = bytes[cursor++])) { + // This check was inspired by + // java/com/google/protobuf/Utf8.java + // Tricky optimized form of: + // valid 4-byte leading byte? + // if (byte1 > (byte) 0xF4 || + // overlong? 4 most significant bits must not all be zero + // byte1 == (byte) 0xF0 && byte2 < (byte) 0x90 || + // codepoint larger than the highest code point (U+10FFFF)? + // byte1 == (byte) 0xF4 && byte2 > (byte) 0x8F) + (((c1 << 28) + (c2 - 0x90)) >> 30) !== 0 || + // We delay reading c3 and c4 until now so than an error in c2 or c1 + // will preserve them for the next loop iteration. + isNotTrailingByte(c3 = bytes[cursor++]) || + isNotTrailingByte(c4 = bytes[cursor++])) { cursor--; // push back c2, c3 or c4 depending on how far we made it invalid(parsingErrorsAreFatal, codeUnits); } else { @@ -177,9 +181,9 @@ jspb.binary.utf8.polyfillDecodeUtf8 = function ( // bytes. This is 21 bits which is too big for a 16 bit utf16 code // unit so we use surrogates. let codepoint = ((c1 & 0x7) << 18) | ((c2 & 0x3F) << 12) | - ((c3 & 0x3F) << 6) | (c4 & 0x3F); + ((c3 & 0x3F) << 6) | (c4 & 0x3F); // Consistency check, this is the valid range for a 4 byte character. - jspb.asserts.assert(codepoint >= 0x10000 && codepoint <= 0x10FFFF); + assert(codepoint >= 0x10000 && codepoint <= 0x10FFFF); // Surrogates formula from wikipedia. // 1. Subtract 0x10000 from codepoint codepoint -= 0x10000; @@ -203,15 +207,15 @@ jspb.binary.utf8.polyfillDecodeUtf8 = function ( } } // ensure we don't overflow or underflow - jspb.asserts.assert(cursor === end, `expected ${cursor} === ${end}`); + assert(cursor === end, `expected ${cursor} === ${end}`); return codeUnitsToString(result, codeUnits); } /** @type {boolean|undefined} */ let isFatalTextDecoderCachableAfterThrowing_ = - // chrome version >= 2020 are not subject to https://crbug.com/910292 - goog.FEATURESET_YEAR >= 2020 ? true : undefined; + // chrome version >= 2020 are not subject to https://crbug.com/910292 + goog.FEATURESET_YEAR >= 2020 ? true : undefined; /** @return {boolean} */ function isFatalTextDecoderCachableAfterThrowing(/** !TextDecoder */ decoder) { @@ -250,7 +254,7 @@ let fatalDecoderInstance; function getFatalDecoderInstance() { let instance = fatalDecoderInstance; if (!instance) { - instance = fatalDecoderInstance = new TextDecoder('utf-8', { fatal: true }); + instance = fatalDecoderInstance = new TextDecoder('utf-8', {fatal: true}); } return instance; } @@ -263,7 +267,7 @@ function getNonFatalDecoderInstance() { let instance = nonFatalDecoderInstance; if (!instance) { instance = nonFatalDecoderInstance = - new TextDecoder('utf-8', { fatal: false }); + new TextDecoder('utf-8', {fatal: false}); } return instance; } @@ -277,25 +281,25 @@ function getNonFatalDecoderInstance() { function subarray( /** !Uint8Array*/ bytes, /** number */ offset, /** number */ end) { return offset === 0 && end === bytes.length ? bytes : - bytes.subarray(offset, end); + bytes.subarray(offset, end); } /** * @return {string} */ -jspb.binary.utf8.textDecoderDecodeUtf8 = function ( +function textDecoderDecodeUtf8( /** !Uint8Array*/ bytes, /** number */ offset, /** number */ length, /** boolean*/ parsingErrorsAreFatal) { const /** !TextDecoder */ decoder = parsingErrorsAreFatal ? - getFatalDecoderInstance() : - getNonFatalDecoderInstance(); + getFatalDecoderInstance() : + getNonFatalDecoderInstance(); bytes = subarray(bytes, offset, offset + length); try { return decoder.decode(bytes); } catch (e) { if (parsingErrorsAreFatal && - !isFatalTextDecoderCachableAfterThrowing(decoder)) { + !isFatalTextDecoderCachableAfterThrowing(decoder)) { fatalDecoderInstance = undefined; } throw e; @@ -303,60 +307,57 @@ jspb.binary.utf8.textDecoderDecodeUtf8 = function ( } /** @const {boolean} */ -const useTextDecoderDecode = - USE_TEXT_ENCODING || typeof TextDecoder !== 'undefined'; +const useTextDecoderDecode = USE_TEXT_ENCODING && + (ASSUME_TEXT_ENCODING_AVAILABLE || typeof TextDecoder !== 'undefined'); /** * A utf8 decoding routine either based upon TextDecoder if available or using * our polyfill implementation * @return {string} */ -jspb.binary.utf8.decodeUtf8 = function ( +function decodeUtf8( /** !Uint8Array*/ bytes, /** number */ offset, /** number */ length, /** boolean*/ parsingErrorsAreFatal) { return useTextDecoderDecode ? - jspb.binary.utf8.textDecoderDecodeUtf8(bytes, offset, length, parsingErrorsAreFatal) : - jspb.binary.utf8.polyfillDecodeUtf8(bytes, offset, length, parsingErrorsAreFatal); + textDecoderDecodeUtf8(bytes, offset, length, parsingErrorsAreFatal) : + polyfillDecodeUtf8(bytes, offset, length, parsingErrorsAreFatal); } /** @type {!TextEncoder|undefined} */ let textEncoderInstance; /** @return {!Uint8Array} */ -jspb.binary.utf8.textEncoderEncode = function ( +function textEncoderEncode( /** string */ s, /** boolean */ rejectUnpairedSurrogates) { if (rejectUnpairedSurrogates) { - jspb.binary.utf8.checkWellFormed(s); - } - - if (!textEncoderInstance) { - textEncoderInstance = new TextEncoder(); + checkWellFormed(s); } - return textEncoderInstance.encode(s); + return (textEncoderInstance ||= new TextEncoder()).encode(s); } +// No externs on isWellFormed until the JSCompiler implements a polyfill. +const /** string */ IS_WELL_FORMED = 'isWellFormed'; + // isWellFormed landed in major browsers in early 2023 so it will only be // definitely available in 2024 See // http://go/mdn/JavaScript/Reference/Global_Objects/String/isWellFormed const /** boolean */ HAS_WELL_FORMED_METHOD = goog.FEATURESET_YEAR > 2023 || - typeof String.prototype.isWellFormed === 'function'; + typeof String.prototype[IS_WELL_FORMED] === 'function'; -jspb.binary.utf8.checkWellFormed = function (/** string */ text) { +function checkWellFormed(/** string */ text) { if (HAS_WELL_FORMED_METHOD ? - // Externs don't contain the definition of this function yet. - // http://go/mdn/JavaScript/Reference/Global_Objects/String/isWellFormed - !(/** @type{{isWellFormed:function():boolean}}*/ ( - /** @type {?} */ (text)) - .isWellFormed()) : - /(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])/ - .test(text)) { + // Externs don't contain the definition of this function yet. + // http://go/mdn/JavaScript/Reference/Global_Objects/String/isWellFormed + !(/** @type {?} */ (text)[IS_WELL_FORMED]()) : + /(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])/ + .test(text)) { throw new Error('Found an unpaired surrogate'); } } /** @return {!Uint8Array} */ -jspb.binary.utf8.polyfillEncode = function ( +function polyfillEncode( /** string */ s, /** boolean */ rejectUnpairedSurrogates) { let bi = 0; // The worse case is that every character requires 3 output bytes, so we @@ -371,7 +372,7 @@ jspb.binary.utf8.polyfillEncode = function ( buffer[bi++] = (c >> 6) | 0xC0; buffer[bi++] = (c & 63) | 0x80; } else { - jspb.asserts.assert(c < 65536); + assert(c < 65536); // Look for surrogates // First check if it is surrogate range if (c >= MIN_SURROGATE && c <= MAX_SURROGATE) { @@ -381,7 +382,7 @@ jspb.binary.utf8.polyfillEncode = function ( if (c2 >= MIN_LOW_SURROGATE && c2 <= MAX_LOW_SURROGATE) { // http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae const codePoint = - (c - MIN_SURROGATE) * 0x400 + c2 - MIN_LOW_SURROGATE + 0x10000; + (c - MIN_SURROGATE) * 0x400 + c2 - MIN_LOW_SURROGATE + 0x10000; buffer[bi++] = (codePoint >> 18) | 0xF0; buffer[bi++] = ((codePoint >> 12) & 63) | 0x80; buffer[bi++] = ((codePoint >> 6) & 63) | 0x80; @@ -408,18 +409,30 @@ jspb.binary.utf8.polyfillEncode = function ( } /** @const {boolean} */ -const useTextEncoderEncode = - (USE_TEXT_ENCODING || typeof TextEncoder !== 'undefined'); +const useTextEncoderEncode = USE_TEXT_ENCODING && + (ASSUME_TEXT_ENCODING_AVAILABLE || typeof TextEncoder !== 'undefined'); /** * A utf8 encoding routine either based upon TextEncoder if available or using * our polyfill implementation * @return {!Uint8Array} */ -jspb.binary.utf8.encodeUtf8 = function ( +function encodeUtf8( /**string*/ string, /** boolean=*/ rejectUnpairedSurrogates = false) { - jspb.asserts.assertString(string); + assertString(string); return useTextEncoderEncode ? - jspb.binary.utf8.textEncoderEncode(string, rejectUnpairedSurrogates) : - jspb.binary.utf8.polyfillEncode(string, rejectUnpairedSurrogates); + textEncoderEncode(string, rejectUnpairedSurrogates) : + polyfillEncode(string, rejectUnpairedSurrogates); } + + +exports = { + decodeUtf8, + encodeUtf8, + checkWellFormed, + // The following are exposed directly for testing/benchmarking purposes only. + textDecoderDecodeUtf8, + polyfillDecodeUtf8, + textEncoderEncode, + polyfillEncode, +}; diff --git a/binary/utils.js b/binary/utils.js old mode 100644 new mode 100755 index 037386a..6664fe2 --- a/binary/utils.js +++ b/binary/utils.js @@ -1,50 +1,56 @@ -// Protocol Buffers - Google's data interchange format -// Copyright 2008 Google Inc. All rights reserved. -// https://protobuf.dev/ -// -// 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 This file contains helper code used by jspb.BinaryReader + * @fileoverview This file contains helper code used by BinaryReader * and BinaryWriter. * - * @suppress {missingRequire} TODO(b/152540451): this shouldn't be needed + * * @author aappleby@google.com (Austin Appleby) */ -goog.provide('jspb.utils'); +goog.module('jspb.utils'); +goog.module.declareLegacyNamespace(); -goog.require('goog.crypt'); -goog.require('goog.crypt.base64'); -goog.require('goog.string'); +const BinaryConstants = goog.require('jspb.BinaryConstants'); +const { assert } = goog.require('goog.asserts'); +const { isBigIntAvailable } = goog.require('jspb.internal_options'); +const { ByteString } = goog.require('jspb.bytestring'); +const { decodeStringToUint8Array } = goog.require('goog.crypt.base64'); +const { unsafeUint8ArrayFromByteString } = goog.require('jspb.unsafe_bytestring'); + +/** + * Flag for browser support of Uint8Array slicing. + * + * This isn't available in every browser that supports Uint8Array + * + * See https://caniuse.com/mdn-javascript_builtins_typedarray_slice and + * go/jscompiler-flags#browser-featureset-year-options + * @const {boolean} + */ +const SUPPORTS_UINT8ARRAY_SLICING = goog.FEATURESET_YEAR >= 2018 || + (typeof Uint8Array.prototype.slice === 'function'); -goog.require('jspb.asserts'); -goog.require('jspb.BinaryConstants'); +/** @const {number} */ +const MAX_SCRATCHPAD_BYTES = 8; + +/** + * Returns a copy of a slice of a Uint8Array. + * + * @param {!Uint8Array} arr the input array to slice. + * @param {number} startIdx The starting index of the slice. + * @param {number} endIdx The ending index of the slice. + * @return {!Uint8Array} the array slice. + */ +function sliceUint8Array(arr, startIdx, endIdx) { + // See https://jsbench.me/ysl0kb8y54/1 + // This test is significantly faster than native slice performance for an + // empty slice, and empty slices are not uncommon. + if (startIdx === endIdx) { + return new Uint8Array(0); + } + return SUPPORTS_UINT8ARRAY_SLICING ? + arr.slice(startIdx, endIdx) : + new Uint8Array(arr.subarray(startIdx, endIdx)); +} /** * Javascript can't natively handle 64-bit data types, so to manipulate them we @@ -56,102 +62,70 @@ goog.require('jspb.BinaryConstants'); * integer, this temporary value will contain the low 32 bits of that integer. * If the original value was a double, this temporary value will contain the * low 32 bits of the binary representation of that double, etcetera. + * + * This value may be signed or unsigned for the same bit pattern. Coerce to a + * specific interpretation before use if needed. * @type {number} - * @private */ -jspb.utils.split64Low = 0; +let split64Low = 0; /** * And correspondingly, this temporary variable will contain the high 32 bits * of whatever value was split. * @type {number} - * @private */ -jspb.utils.split64High = 0; - - -/** - * @return {number} - * @export - */ -jspb.utils.getSplit64Low = function() { - return jspb.utils.split64Low; -} - -/** - * @return {number} - * @export - */ -jspb.utils.getSplit64High = function() { - return jspb.utils.split64High; -} +let split64High = 0; +/** @type {!DataView|undefined} */ +let scratchpad; /** * Splits an unsigned Javascript integer into two 32-bit halves and stores it * in the temp values above. * @param {number} value The number to split. - * @export */ -jspb.utils.splitUint64 = function(value) { +function splitUint64(value) { // Extract low 32 bits and high 32 bits as unsigned integers. - var lowBits = value >>> 0; - var highBits = - Math.floor((value - lowBits) / jspb.BinaryConstants.TWO_TO_32) >>> 0; - - jspb.utils.split64Low = lowBits; - jspb.utils.split64High = highBits; -}; + const lowBits = value >>> 0; + const highBits = ((value - lowBits) / BinaryConstants.TWO_TO_32) >>> 0; + split64Low = lowBits; + split64High = highBits; +} /** * Splits a signed Javascript integer into two 32-bit halves and stores it in * the temp values above. * @param {number} value The number to split. - * @export */ -jspb.utils.splitInt64 = function(value) { - // Convert to sign-magnitude representation. - var sign = (value < 0); - value = Math.abs(value); - - // Extract low 32 bits and high 32 bits as unsigned integers. - var lowBits = value >>> 0; - var highBits = Math.floor((value - lowBits) / jspb.BinaryConstants.TWO_TO_32); - highBits = highBits >>> 0; - +function splitInt64(value) { // Perform two's complement conversion if the sign bit was set. - if (sign) { - highBits = ~highBits >>> 0; - lowBits = ~lowBits >>> 0; - lowBits += 1; - if (lowBits > 0xFFFFFFFF) { - lowBits = 0; - highBits++; - if (highBits > 0xFFFFFFFF) highBits = 0; - } + if (value < 0) { + // Convert to sign-magnitude representation. + splitUint64(0 - value); + const [negLow, negHigh] = negate(split64Low, split64High); + split64Low = negLow >>> 0; + split64High = negHigh >>> 0; + } else { + splitUint64(value); } - - jspb.utils.split64Low = lowBits; - jspb.utils.split64High = highBits; -}; +} /** * Converts a signed Javascript integer into zigzag format, splits it into two * 32-bit halves, and stores it in the temp values above. * @param {number} value The number to split. - * @export */ -jspb.utils.splitZigzag64 = function(value) { +function splitZigzag64(value) { // Convert to sign-magnitude and scale by 2 before we split the value. - var sign = (value < 0); + const sign = (value < 0); value = Math.abs(value) * 2; - jspb.utils.splitUint64(value); - var lowBits = jspb.utils.split64Low; - var highBits = jspb.utils.split64High; + splitUint64(value); + let lowBits = split64Low; + let highBits = split64High; // If the value is negative, subtract 1 from the split representation so we // don't lose the sign bit due to precision issues. @@ -169,196 +143,90 @@ jspb.utils.splitZigzag64 = function(value) { } } - jspb.utils.split64Low = lowBits; - jspb.utils.split64High = highBits; -}; + split64Low = lowBits; + split64High = highBits; +} +/** + * Initialize the scratchpad `DataView` to the given number of bytes and + * returns scratchpad. + * @param {number} numBytes + * @return {!DataView} + */ +function getScratchpad(numBytes) { + assert(numBytes <= MAX_SCRATCHPAD_BYTES); + return scratchpad || + (scratchpad = new DataView(new ArrayBuffer(MAX_SCRATCHPAD_BYTES))); +} /** * Converts a floating-point number into 32-bit IEEE representation and stores * it in the temp values above. - * @param {number} value - * @export - */ -jspb.utils.splitFloat32 = function(value) { - var sign = (value < 0) ? 1 : 0; - value = sign ? -value : value; - var exp; - var mant; - - // Handle zeros. - if (value === 0) { - if ((1 / value) > 0) { - // Positive zero. - jspb.utils.split64High = 0; - jspb.utils.split64Low = 0x00000000; - } else { - // Negative zero. - jspb.utils.split64High = 0; - jspb.utils.split64Low = 0x80000000; - } - return; - } - - // Handle nans. - if (isNaN(value)) { - jspb.utils.split64High = 0; - jspb.utils.split64Low = 0x7FFFFFFF; - return; - } - - // Handle infinities. - if (value > jspb.BinaryConstants.FLOAT32_MAX) { - jspb.utils.split64High = 0; - jspb.utils.split64Low = ((sign << 31) | (0x7F800000)) >>> 0; - return; - } - - // Handle denormals. - if (value < jspb.BinaryConstants.FLOAT32_MIN) { - // Number is a denormal. - mant = Math.round(value / Math.pow(2, -149)); - jspb.utils.split64High = 0; - jspb.utils.split64Low = ((sign << 31) | mant) >>> 0; - return; - } - - exp = Math.floor(Math.log(value) / Math.LN2); - mant = value * Math.pow(2, -exp); - mant = Math.round(mant * jspb.BinaryConstants.TWO_TO_23); - if (mant >= 0x1000000) { - ++exp; - } - mant = mant & 0x7FFFFF; - - jspb.utils.split64High = 0; - jspb.utils.split64Low = ((sign << 31) | ((exp + 127) << 23) | mant) >>> 0; -}; + * @param {number|string} value to split. Accepts 'Infinity'/'-Infinity'/'NaN' + * for JSPB wire format compatibility. + */ +function splitFloat32(value) { + const scratch = getScratchpad(/* numBytes= */ 4); + // See go/proto-encoding#cheat-sheet re: little endian. + scratch.setFloat32(0, +value, /* littleEndian= */ true); + split64High = 0; + split64Low = scratch.getUint32(0, /* littleEndian = */ true); +} /** * Converts a floating-point number into 64-bit IEEE representation and stores * it in the temp values above. - * @param {number} value - * @export - */ -jspb.utils.splitFloat64 = function(value) { - var sign = (value < 0) ? 1 : 0; - value = sign ? -value : value; - - // Handle zeros. - if (value === 0) { - if ((1 / value) > 0) { - // Positive zero. - jspb.utils.split64High = 0x00000000; - jspb.utils.split64Low = 0x00000000; - } else { - // Negative zero. - jspb.utils.split64High = 0x80000000; - jspb.utils.split64Low = 0x00000000; - } - return; - } - - // Handle nans. - if (isNaN(value)) { - jspb.utils.split64High = 0x7FFFFFFF; - jspb.utils.split64Low = 0xFFFFFFFF; - return; - } - - // Handle infinities. - if (value > jspb.BinaryConstants.FLOAT64_MAX) { - jspb.utils.split64High = ((sign << 31) | (0x7FF00000)) >>> 0; - jspb.utils.split64Low = 0; - return; - } - - // Handle denormals. - if (value < jspb.BinaryConstants.FLOAT64_MIN) { - // Number is a denormal. - var mant = value / Math.pow(2, -1074); - var mantHigh = (mant / jspb.BinaryConstants.TWO_TO_32); - jspb.utils.split64High = ((sign << 31) | mantHigh) >>> 0; - jspb.utils.split64Low = (mant >>> 0); - return; - } - - // Compute the least significant exponent needed to represent the magnitude of - // the value by repeadly dividing/multiplying by 2 until the magnitude - // crosses 2. While tempting to use log math to find the exponent, at the - // boundaries of precision, the result can be off by one. - var maxDoubleExponent = 1023; - var minDoubleExponent = -1022; - var x = value; - var exp = 0; - if (x >= 2) { - while (x >= 2 && exp < maxDoubleExponent) { - exp++; - x = x / 2; - } - } else { - while (x < 1 && exp > minDoubleExponent) { - x = x * 2; - exp--; - } - } - var mant = value * Math.pow(2, -exp); - - var mantHigh = (mant * jspb.BinaryConstants.TWO_TO_20) & 0xFFFFF; - var mantLow = (mant * jspb.BinaryConstants.TWO_TO_52) >>> 0; - - jspb.utils.split64High = - ((sign << 31) | ((exp + 1023) << 20) | mantHigh) >>> 0; - jspb.utils.split64Low = mantLow; -}; + * @param {number|string} value to split. Accepts 'Infinity'/'-Infinity'/'NaN' + * for JSPB wire format compatibility. + */ +function splitFloat64(value) { + const scratch = getScratchpad(/* numBytes= */ 8); + // See go/proto-encoding#cheat-sheet re: little endian. + scratch.setFloat64(0, +value, /* littleEndian= */ true); + split64Low = scratch.getUint32(0, /* littleEndian = */ true); + split64High = scratch.getUint32(4, /* littleEndian = */ true); +} /** - * Converts an 8-character hash string into two 32-bit numbers and stores them - * in the temp values above. - * @param {string} hash - * @export + * Converts an 8-byte array into two 32-bit numbers and stores them in the temp + * values above. + * @param {!Array} bytes */ -jspb.utils.splitHash64 = function(hash) { - var a = hash.charCodeAt(0); - var b = hash.charCodeAt(1); - var c = hash.charCodeAt(2); - var d = hash.charCodeAt(3); - var e = hash.charCodeAt(4); - var f = hash.charCodeAt(5); - var g = hash.charCodeAt(6); - var h = hash.charCodeAt(7); - - jspb.utils.split64Low = (a + (b << 8) + (c << 16) + (d << 24)) >>> 0; - jspb.utils.split64High = (e + (f << 8) + (g << 16) + (h << 24)) >>> 0; -}; +function splitBytes64(bytes) { + const [a, b, c, d, e, f, g, h] = bytes; + + split64Low = (a + (b << 8) + (c << 16) + (d << 24)) >>> 0; + split64High = (e + (f << 8) + (g << 16) + (h << 24)) >>> 0; +} /** - * Joins two 32-bit values into a 64-bit unsigned integer. Precision will be - * lost if the result is greater than 2^52. + * Joins two 32-bit values into a 64-bit unsigned integer. Value will be + * returned as a string if it is greater than 2^52 to avoid precision loss. * @param {number} bitsLow * @param {number} bitsHigh * @return {number} - * @export */ -jspb.utils.joinUint64 = function(bitsLow, bitsHigh) { - return bitsHigh * jspb.BinaryConstants.TWO_TO_32 + (bitsLow >>> 0); -}; - +function joinUint64(bitsLow, bitsHigh) { + const maybeUnsafeValue = + bitsHigh * BinaryConstants.TWO_TO_32 + (bitsLow >>> 0); + return Number.isSafeInteger(maybeUnsafeValue) ? + maybeUnsafeValue : /** @type {number} */ + (/** @type {*} */ (joinUnsignedDecimalString(bitsLow, bitsHigh))); +} /** - * Joins two 32-bit values into a 64-bit signed integer. Precision will be lost - * if the result is greater than 2^52. + * Joins two 32-bit values into a 64-bit signed integer. Value will be + * returned as a string if it outside of the safe integer range. * @param {number} bitsLow * @param {number} bitsHigh * @return {number} - * @export */ -jspb.utils.joinInt64 = function(bitsLow, bitsHigh) { +function joinInt64(bitsLow, bitsHigh) { // If the high bit is set, do a manual two's complement conversion. - var sign = (bitsHigh & 0x80000000); + const sign = (bitsHigh & 0x80000000); if (sign) { bitsLow = (~bitsLow + 1) >>> 0; bitsHigh = ~bitsHigh >>> 0; @@ -367,9 +235,25 @@ jspb.utils.joinInt64 = function(bitsLow, bitsHigh) { } } - var result = jspb.utils.joinUint64(bitsLow, bitsHigh); - return sign ? -result : result; -}; + const result = joinUint64(bitsLow, bitsHigh); + if (typeof result === 'number') { + return sign ? -result : result; + } + + return sign ? /** @type {number} */ (/** @type {*} */ ('-' + result)) : + result; +} + +/** + * Converts 32-bit values from standard two's complement encoding to zig-zag + * encoding. + * + * @param {number} value + * @return {number} + */ +function toZigzag32(value) { + return ((value << 1) ^ (value >> 31)) >>> 0; +} /** * Converts split 64-bit values from standard two's complement encoding to @@ -381,9 +265,8 @@ jspb.utils.joinInt64 = function(bitsLow, bitsHigh) { * the result value, takes parameters (lowBits, highBits). * @return {T} * @template T - * @export */ -jspb.utils.toZigzag64 = function(bitsLow, bitsHigh, convert) { +function toZigzag64(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) @@ -393,11 +276,11 @@ jspb.utils.toZigzag64 = function(bitsLow, bitsHigh, convert) { // 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; + const signFlipMask = bitsHigh >> 31; bitsHigh = (bitsHigh << 1 | bitsLow >>> 31) ^ signFlipMask; bitsLow = (bitsLow << 1) ^ signFlipMask; return convert(bitsLow, bitsHigh); -}; +} /** @@ -406,12 +289,21 @@ jspb.utils.toZigzag64 = function(bitsLow, bitsHigh, convert) { * @param {number} bitsLow * @param {number} bitsHigh * @return {number} - * @export */ -jspb.utils.joinZigzag64 = function(bitsLow, bitsHigh) { - return jspb.utils.fromZigzag64(bitsLow, bitsHigh, jspb.utils.joinInt64); -}; +function joinZigzag64(bitsLow, bitsHigh) { + return fromZigzag64(bitsLow, bitsHigh, joinInt64); +} +/** + * Converts 32-bit value from zigzag encoding to standard two's + * complement encoding. + * @param {number} zigzag + * @return {number} + */ +function fromZigzag32(zigzag) { + const signFlipMask = -(zigzag & 1); + return (zigzag >>> 1) ^ signFlipMask; +} /** * Converts split 64-bit values from zigzag encoding to standard two's @@ -423,9 +315,8 @@ jspb.utils.joinZigzag64 = function(bitsLow, bitsHigh) { * the result value, takes parameters (lowBits, highBits). * @return {T} * @template T - * @export */ -jspb.utils.fromZigzag64 = function(bitsLow, bitsHigh, convert) { +function fromZigzag64(bitsLow, bitsHigh, convert) { // 64 bit math is: // signmask = (zigzag & 1) ? -1 : 0; // twosComplement = (zigzag >> 1) ^ signmask; @@ -433,11 +324,11 @@ jspb.utils.fromZigzag64 = function(bitsLow, bitsHigh, convert) { // 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); + const signFlipMask = -(bitsLow & 1); bitsLow = ((bitsLow >>> 1) | (bitsHigh << 31)) ^ signFlipMask; bitsHigh = (bitsHigh >>> 1) ^ signFlipMask; return convert(bitsLow, bitsHigh); -}; +} /** @@ -446,12 +337,11 @@ jspb.utils.fromZigzag64 = function(bitsLow, bitsHigh, convert) { * @param {number} bitsLow The low 32 bits of the binary number; * @param {number} bitsHigh The high 32 bits of the binary number. * @return {number} - * @export */ -jspb.utils.joinFloat32 = function(bitsLow, bitsHigh) { - var sign = ((bitsLow >> 31) * 2 + 1); - var exp = (bitsLow >>> 23) & 0xFF; - var mant = bitsLow & 0x7FFFFF; +function joinFloat32(bitsLow, bitsHigh) { + const sign = ((bitsLow >> 31) * 2 + 1); + const exp = (bitsLow >>> 23) & 0xFF; + const mant = bitsLow & 0x7FFFFF; if (exp == 0xFF) { if (mant) { @@ -467,7 +357,7 @@ jspb.utils.joinFloat32 = function(bitsLow, bitsHigh) { } else { return sign * Math.pow(2, exp - 150) * (mant + Math.pow(2, 23)); } -}; +} /** @@ -476,12 +366,11 @@ jspb.utils.joinFloat32 = function(bitsLow, bitsHigh) { * @param {number} bitsLow The low 32 bits of the binary number; * @param {number} bitsHigh The high 32 bits of the binary number. * @return {number} - * @export */ -jspb.utils.joinFloat64 = function(bitsLow, bitsHigh) { - var sign = ((bitsHigh >> 31) * 2 + 1); - var exp = (bitsHigh >>> 20) & 0x7FF; - var mant = jspb.BinaryConstants.TWO_TO_32 * (bitsHigh & 0xFFFFF) + bitsLow; +function joinFloat64(bitsLow, bitsHigh) { + const sign = ((bitsHigh >> 31) * 2 + 1); + const exp = (bitsHigh >>> 20) & 0x7FF; + const mant = BinaryConstants.TWO_TO_32 * (bitsHigh & 0xFFFFF) + bitsLow; if (exp == 0x7FF) { if (mant) { @@ -495,46 +384,9 @@ jspb.utils.joinFloat64 = function(bitsLow, bitsHigh) { // Denormal. return sign * Math.pow(2, -1074) * mant; } else { - return sign * Math.pow(2, exp - 1075) * - (mant + jspb.BinaryConstants.TWO_TO_52); + return sign * Math.pow(2, exp - 1075) * (mant + BinaryConstants.TWO_TO_52); } -}; - - -/** - * Joins two 32-bit values into an 8-character hash string. - * @param {number} bitsLow - * @param {number} bitsHigh - * @return {string} - * @export - */ -jspb.utils.joinHash64 = function(bitsLow, bitsHigh) { - var a = (bitsLow >>> 0) & 0xFF; - var b = (bitsLow >>> 8) & 0xFF; - var c = (bitsLow >>> 16) & 0xFF; - var d = (bitsLow >>> 24) & 0xFF; - var e = (bitsHigh >>> 0) & 0xFF; - var f = (bitsHigh >>> 8) & 0xFF; - var g = (bitsHigh >>> 16) & 0xFF; - var h = (bitsHigh >>> 24) & 0xFF; - - return String.fromCharCode(a, b, c, d, e, f, g, h); -}; - -/** - * Individual digits for number->string conversion. - * @const {!Array} - * @export - */ -jspb.utils.DIGITS = [ - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' -]; - -/** @const @private {number} '0' */ -jspb.utils.ZERO_CHAR_CODE_ = 48; - -/** @const @private {number} 'a' */ -jspb.utils.A_CHAR_CODE_ = 97; +} /** * Losslessly converts a 64-bit unsigned integer in 32:32 split representation @@ -542,65 +394,88 @@ jspb.utils.A_CHAR_CODE_ = 97; * @param {number} bitsLow The low 32 bits of the binary number; * @param {number} bitsHigh The high 32 bits of the binary number. * @return {string} The binary number represented as a string. - * @export */ -jspb.utils.joinUnsignedDecimalString = function(bitsLow, bitsHigh) { +function joinUnsignedDecimalString(bitsLow, bitsHigh) { + // Must ensure the values are handled as unsigned, any bitwise operations + // on the input arguments would have turned them into signed values (e.g. + // zigzag decoding). + bitsHigh = bitsHigh >>> 0; + bitsLow = bitsLow >>> 0; // Skip the expensive conversion if the number is small enough to use the // built-in conversions. + // Number.MAX_SAFE_INTEGER = 0x001FFFFF FFFFFFFF, thus any number with + // bitsHigh <= 0x1FFFFF can be safely expressed with a double and retain + // integer precision. + // Proven by: Number.isSafeInteger(0x1FFFFF * 2**32 + 0xFFFFFFFF) == true. + // Even when BigInt is supported, it's faster to avoid the conversion to + // bigint and back. if (bitsHigh <= 0x1FFFFF) { - return '' + jspb.utils.joinUint64(bitsLow, bitsHigh); + return '' + (BinaryConstants.TWO_TO_32 * bitsHigh + bitsLow); + } else if (isBigIntAvailable()) { + return '' + (BigInt(bitsHigh) << BigInt(32) | BigInt(bitsLow)); } + return joinUnsignedDecimalStringFallback(bitsLow, bitsHigh); +} +/** + * Losslessly converts a 64-bit unsigned integer in 32:32 split representation + * into a decimal string without using BigInt. + * @param {number} bitsLow The unsigned low 32 bits of the binary number; + * @param {number} bitsHigh The unsigned high 32 bits of the binary number. + * @return {string} The binary number represented as a string. + * @package For Testing only. + */ +function joinUnsignedDecimalStringFallback(bitsLow, bitsHigh) { // What this code is doing is essentially converting the input number from // base-2 to base-1e7, which allows us to represent the 64-bit range with // only 3 (very large) digits. Those digits are then trivial to convert to // a base-10 string. - // The magic numbers used here are - - // 2^24 = 16777216 = (1,6777216) in base-1e7. - // 2^48 = 281474976710656 = (2,8147497,6710656) in base-1e7. - // Split 32:32 representation into 16:24:24 representation so our // intermediate digits don't overflow. - var low = bitsLow & 0xFFFFFF; - var mid = (((bitsLow >>> 24) | (bitsHigh << 8)) >>> 0) & 0xFFFFFF; - var high = (bitsHigh >> 16) & 0xFFFF; + const low = bitsLow & LOW_24_BITS; + const mid = ((bitsLow >>> 24) | (bitsHigh << 8)) & LOW_24_BITS; + const high = (bitsHigh >> 16) & LOW_16_BITS; // Assemble our three base-1e7 digits, ignoring carries. The maximum // value in a digit at this step is representable as a 48-bit integer, which // can be stored in a 64-bit floating point number. - var digitA = low + (mid * 6777216) + (high * 6710656); - var digitB = mid + (high * 8147497); - var digitC = (high * 2); + + // The magic numbers used here are - + // 2^24 = 16777216 = (1,6777216) in base-1e7. + // 2^48 = 281474976710656 = (2,8147497,6710656) in base-1e7. + let digitA = low + (mid * 6777216) + (high * 6710656); + let digitB = mid + (high * 8147497); + let digitC = (high * 2); // Apply carries from A to B and from B to C. - var base = 10000000; + const base = 10000000; if (digitA >= base) { - digitB += Math.floor(digitA / base); + digitB += (digitA / base) >>> 0; digitA %= base; } if (digitB >= base) { - digitC += Math.floor(digitB / base); + digitC += (digitB / base) >>> 0; digitB %= base; } - // Convert base-1e7 digits to base-10, with optional leading zeroes. - function decimalFrom1e7(digit1e7, needLeadingZeros) { - var partial = digit1e7 ? String(digit1e7) : ''; - if (needLeadingZeros) { - return '0000000'.slice(partial.length) + partial; - } - return partial; - } - - return decimalFrom1e7(digitC, /*needLeadingZeros=*/ 0) + - decimalFrom1e7(digitB, /*needLeadingZeros=*/ digitC) + - // If the final 1e7 digit didn't need leading zeros, we would have - // returned via the trivial code path at the top. - decimalFrom1e7(digitA, /*needLeadingZeros=*/ 1); -}; + // If digitC is 0, then we should have returned in the trivial code path + // at the top for non-safe integers. Given this, we can assume both digitB + // and digitA need leading zeros. + assert(digitC); + return digitC + decimalFrom1e7WithLeadingZeros(digitB) + + decimalFrom1e7WithLeadingZeros(digitA); +} +/** + * @param {number} digit1e7 Number < 1e7 + * @return {string} Decimal representation of digit1e7 with leading zeros. + */ +function decimalFrom1e7WithLeadingZeros(digit1e7) { + const partial = String(digit1e7); + return '0000000'.slice(partial.length) + partial; +} /** * Losslessly converts a 64-bit signed integer in 32:32 split representation @@ -608,218 +483,156 @@ jspb.utils.joinUnsignedDecimalString = function(bitsLow, bitsHigh) { * @param {number} bitsLow The low 32 bits of the binary number; * @param {number} bitsHigh The high 32 bits of the binary number. * @return {string} The binary number represented as a string. - * @export */ -jspb.utils.joinSignedDecimalString = function(bitsLow, bitsHigh) { - // If we're treating the input as a signed value and the high bit is set, do - // a manual two's complement conversion before the decimal conversion. - var negative = (bitsHigh & 0x80000000); +function joinSignedDecimalString(bitsLow, bitsHigh) { + const negative = (bitsHigh & 0x80000000); if (negative) { - bitsLow = (~bitsLow + 1) >>> 0; - var carry = (bitsLow == 0) ? 1 : 0; - bitsHigh = (~bitsHigh + carry) >>> 0; + if (isBigIntAvailable()) { + return '' + + ((BigInt(bitsHigh | 0) << BigInt(32)) | BigInt(bitsLow >>> 0)); + } + return joinNegativeDecimalStringFallback(bitsLow, bitsHigh); + } else { + return joinUnsignedDecimalString(bitsLow, bitsHigh); } - - var result = jspb.utils.joinUnsignedDecimalString(bitsLow, bitsHigh); - return negative ? '-' + result : result; -}; - +} /** - * Convert an 8-character hash string representing either a signed or unsigned - * 64-bit integer into its decimal representation without losing accuracy. - * @param {string} hash The hash string to convert. - * @param {boolean} signed True if we should treat the hash string as encoding - * a signed integer. - * @return {string} - * @export + * Losslessly converts a 64-bit signed integer in 32:32 split representation + * into a number or decimal string, using a number when the value is between + * [Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER], or decimal string + * otherwise. + * + * @param {number} bitsLow The low 32 bits of the binary number; + * @param {number} bitsHigh The high 32 bits of the binary number. + * @return {number|string} The number represented as a number or string. */ -jspb.utils.hash64ToDecimalString = function(hash, signed) { - jspb.utils.splitHash64(hash); - var bitsLow = jspb.utils.split64Low; - var bitsHigh = jspb.utils.split64High; - return signed ? jspb.utils.joinSignedDecimalString(bitsLow, bitsHigh) : - jspb.utils.joinUnsignedDecimalString(bitsLow, bitsHigh); -}; - +function joinSignedNumberOrDecimalString(bitsLow, bitsHigh) { + const possiblyUnsafe = joinInt64(bitsLow, bitsHigh); + if (Number.isSafeInteger(possiblyUnsafe)) { + return possiblyUnsafe; + } + return joinSignedDecimalString(bitsLow, bitsHigh); +} /** - * Converts an array of 8-character hash strings into their decimal - * representations. - * @param {!Array} hashes The array of hash strings to convert. - * @param {boolean} signed True if we should treat the hash string as encoding - * a signed integer. - * @return {!Array} - * @export + * Losslessly converts a 64-bit unsigned integer in 32:32 split representation + * into a number or decimal string, using a number when the value is between + * [0, Number.MAX_SAFE_INTEGER], or decimal string otherwise. + * + * @param {number} bitsLow The low 32 bits of the binary number; + * @param {number} bitsHigh The high 32 bits of the binary number. + * @return {number|string} The number represented as a number or string. */ -jspb.utils.hash64ArrayToDecimalStrings = function(hashes, signed) { - var result = new Array(hashes.length); - for (var i = 0; i < hashes.length; i++) { - result[i] = jspb.utils.hash64ToDecimalString(hashes[i], signed); +function joinUnsignedNumberOrDecimalString(bitsLow, bitsHigh) { + bitsHigh >>>= 0; + const possiblyUnsafe = joinUint64(bitsLow, bitsHigh); + if (Number.isSafeInteger(possiblyUnsafe)) { + return possiblyUnsafe; } - return result; -}; - + return joinUnsignedDecimalString(bitsLow, bitsHigh); +} /** - * Converts a signed or unsigned decimal string into its hash string - * representation. - * @param {string} dec - * @return {string} - * @export + * Losslessly converts a 64-bit signed integer in 32:32 split representation + * into a decimal string. + * @param {number} bitsLow The low 32 bits of the binary number; + * @param {number} bitsHigh The high 32 bits of the binary number. + * @return {string} The binary number represented as a string. + * @package For Testing only. */ -jspb.utils.decimalStringToHash64 = function(dec) { - jspb.asserts.assert(dec.length > 0); - - // Check for minus sign. - var minus = false; - if (dec[0] === '-') { - minus = true; - dec = dec.slice(1); - } - - // Store result as a byte array. - var resultBytes = [0, 0, 0, 0, 0, 0, 0, 0]; - - // Set result to m*result + c. - function muladd(m, c) { - for (var i = 0; i < 8 && (m !== 1 || c > 0); i++) { - var r = m * resultBytes[i] + c; - resultBytes[i] = r & 0xFF; - c = r >>> 8; - } - } - - // Negate the result bits. - function neg() { - for (var i = 0; i < 8; i++) { - resultBytes[i] = (~resultBytes[i]) & 0xFF; - } - } - - // For each decimal digit, set result to 10*result + digit. - for (var i = 0; i < dec.length; i++) { - muladd(10, dec.charCodeAt(i) - jspb.utils.ZERO_CHAR_CODE_); - } - - // If there's a minus sign, convert into two's complement. - if (minus) { - neg(); - muladd(1, 1); - } - - return goog.crypt.byteArrayToString(resultBytes); -}; +function joinNegativeDecimalStringFallback(bitsLow, bitsHigh) { + // Do a manual two's complement conversion before the decimal conversion. + const [negLow, negHigh] = negate(bitsLow, bitsHigh); + bitsLow = negLow; + bitsHigh = negHigh; + return '-' + joinUnsignedDecimalString(bitsLow, bitsHigh); +} /** * Converts a signed or unsigned decimal string into two 32-bit halves, and - * stores them in the temp variables listed above. + * stores them in the temp variables listed above. Only the lower 64 bits of the + * value are kept. * @param {string} value The decimal string to convert. - * @export */ -jspb.utils.splitDecimalString = function(value) { - jspb.utils.splitHash64(jspb.utils.decimalStringToHash64(value)); -}; +function splitDecimalString(value) { + assert(value.length > 0); -/** - * @param {number} nibble A 4-bit integer. - * @return {string} - * @private - */ -jspb.utils.toHexDigit_ = function(nibble) { - return String.fromCharCode( - nibble < 10 ? jspb.utils.ZERO_CHAR_CODE_ + nibble : - jspb.utils.A_CHAR_CODE_ - 10 + nibble); -}; - -/** - * @param {number} hexCharCode - * @return {number} - * @private - */ -jspb.utils.fromHexCharCode_ = function(hexCharCode) { - if (hexCharCode >= jspb.utils.A_CHAR_CODE_) { - return hexCharCode - jspb.utils.A_CHAR_CODE_ + 10; + // Strings that are shorter than MAX_SAFE_INTEGER are sure to be safe + // to parse directly to a double for conversion with Int64.fromNumber. + if (value.length < MAX_SAFE_INTEGER_DECIMAL_LENGTH) { + splitInt64(Number(value)); + return; } - return hexCharCode - jspb.utils.ZERO_CHAR_CODE_; -}; -/** - * Converts an 8-character hash string into its hexadecimal representation. - * @param {string} hash - * @return {string} - * @export - */ -jspb.utils.hash64ToHexString = function(hash) { - var temp = new Array(18); - temp[0] = '0'; - temp[1] = 'x'; - - for (var i = 0; i < 8; i++) { - var c = hash.charCodeAt(7 - i); - temp[i * 2 + 2] = jspb.utils.toHexDigit_(c >> 4); - temp[i * 2 + 3] = jspb.utils.toHexDigit_(c & 0xF); + if (isBigIntAvailable()) { + const bigInt = BigInt(value); + split64Low = Number(bigInt & BigInt(ALL_32_BITS)) >>> 0; + split64High = Number((bigInt >> BigInt(32)) & BigInt(ALL_32_BITS)); + } else { + splitDecimalStringFallback(value); } - - var result = temp.join(''); - return result; -}; - +} /** - * Converts a '0x<16 digits>' hex string into its hash string representation. - * @param {string} hex - * @return {string} - * @export + * Converts a signed or unsigned decimal string into two 32-bit halves, and + * stores them in the temp variables listed above. Only the lower 64 bits of the + * value are kept. + * @param {string} value The decimal string to convert. + * @package For Testing only. */ -jspb.utils.hexStringToHash64 = function(hex) { - hex = hex.toLowerCase(); - jspb.asserts.assert(hex.length == 18); - jspb.asserts.assert(hex[0] == '0'); - jspb.asserts.assert(hex[1] == 'x'); - - var result = ''; - for (var i = 0; i < 8; i++) { - var hi = jspb.utils.fromHexCharCode_(hex.charCodeAt(i * 2 + 2)); - var lo = jspb.utils.fromHexCharCode_(hex.charCodeAt(i * 2 + 3)); - result = String.fromCharCode(hi * 16 + lo) + result; +function splitDecimalStringFallback(value) { + assert(value.length > 0); + // Check for minus sign. + const firstDigitIndex = +(value[0] === '-'); + split64Low = 0; + split64High = 0; + const end = value.length; + // Work 6 decimal digits at a time, acting like we're converting base 1e6 + // digits to binary. This is safe to do with floating point math because + // Number.isSafeInteger(ALL_32_BITS * 1e6) == true. + const base = 1e6; + for (let sliceStart = 0 + firstDigitIndex, + sliceEnd = (end - firstDigitIndex) % 6 + firstDigitIndex; + sliceEnd <= end; sliceStart = sliceEnd, sliceEnd += 6) { + const digit1e6 = Number(value.slice(sliceStart, sliceEnd)); + split64High *= base; + split64Low = split64Low * base + digit1e6; + // Carry bits from split64Low to + if (split64Low >= BinaryConstants.TWO_TO_32) { + split64High += Math.trunc(split64Low / BinaryConstants.TWO_TO_32); + // Drop any bits higher than 64 as we accumulate + split64High = split64High >>> 0; + split64Low = split64Low >>> 0; + } } - return result; -}; - - -/** - * Convert an 8-character hash string representing either a signed or unsigned - * 64-bit integer into a Javascript number. Will lose accuracy if the result is - * larger than 2^52. - * @param {string} hash The hash string to convert. - * @param {boolean} signed True if the has should be interpreted as a signed - * number. - * @return {number} - * @export - */ -jspb.utils.hash64ToNumber = function(hash, signed) { - jspb.utils.splitHash64(hash); - var bitsLow = jspb.utils.split64Low; - var bitsHigh = jspb.utils.split64High; - return signed ? jspb.utils.joinInt64(bitsLow, bitsHigh) : - jspb.utils.joinUint64(bitsLow, bitsHigh); -}; - + if (firstDigitIndex /* != 0 */) { + const [negLow, negHigh] = negate(split64Low, split64High); + split64Low = negLow; + split64High = negHigh; + } +} /** - * Convert a Javascript number into an 8-character hash string. Will lose - * precision if the value is non-integral or greater than 2^64. - * @param {number} value The integer to convert. - * @return {string} - * @export + * @param {number} lowBits + * @param {number} highBits + * @return {!Array} [low, high] words of the result. + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Signed_32-bit_integers */ -jspb.utils.numberToHash64 = function(value) { - jspb.utils.splitInt64(value); - return jspb.utils.joinHash64(jspb.utils.split64Low, jspb.utils.split64High); -}; +function negate(lowBits, highBits) { + highBits = ~highBits; + if (lowBits) { + lowBits = ~lowBits + 1; + } else { + // If lowBits is 0, then bitwise-not is 0xFFFFFFFF, + // adding 1 to that, results in 0x100000000, which leaves + // the low bits 0x0 and simply adds one to the high bits. + highBits += 1; + } + return [lowBits, highBits]; +} /** @@ -828,12 +641,11 @@ jspb.utils.numberToHash64 = function(value) { * @param {number} start The starting point in the buffer to scan. * @param {number} end The end point in the buffer to scan. * @return {number} The number of varints in the buffer. - * @export */ -jspb.utils.countVarints = function(buffer, start, end) { +function countVarints(buffer, start, end) { // Count how many high bits of each byte were set in the buffer. - var count = 0; - for (var i = start; i < end; i++) { + let count = 0; + for (let i = start; i < end; i++) { count += buffer[i] >> 7; } @@ -841,7 +653,7 @@ jspb.utils.countVarints = function(buffer, start, end) { // the number of non-terminal bytes in the buffer (those with the high bit // set). return (end - start) - count; -}; +} /** @@ -852,12 +664,11 @@ jspb.utils.countVarints = function(buffer, start, end) { * @param {number} end The end point in the buffer to scan. * @param {number} field The field number to count. * @return {number} The number of matching fields in the buffer. - * @export */ -jspb.utils.countVarintFields = function(buffer, start, end, field) { - var count = 0; - var cursor = start; - var tag = field * 8 + jspb.BinaryConstants.WireType.VARINT; +function countVarintFields(buffer, start, end, field) { + let count = 0; + let cursor = start; + const tag = field * 8 + BinaryConstants.WireType.VARINT; if (tag < 128) { // Single-byte field tag, we can use a slightly quicker count. @@ -870,14 +681,14 @@ jspb.utils.countVarintFields = function(buffer, start, end, field) { // Skip the varint. while (1) { - var x = buffer[cursor++]; + const x = buffer[cursor++]; if ((x & 0x80) == 0) break; } } } else { while (cursor < end) { // Skip the field tag, or exit if we find a non-matching tag. - var temp = tag; + let temp = tag; while (temp > 128) { if (buffer[cursor] != ((temp & 0x7F) | 0x80)) return count; cursor++; @@ -890,13 +701,13 @@ jspb.utils.countVarintFields = function(buffer, start, end, field) { // Skip the varint. while (1) { - var x = buffer[cursor++]; + const x = buffer[cursor++]; if ((x & 0x80) == 0) break; } } } return count; -}; +} /** @@ -910,9 +721,9 @@ jspb.utils.countVarintFields = function(buffer, start, end, field) { * @return {number} The number of fields with a matching tag in the buffer. * @private */ -jspb.utils.countFixedFields_ = function(buffer, start, end, tag, stride) { - var count = 0; - var cursor = start; +function countFixedFields_(buffer, start, end, tag, stride) { + let count = 0; + let cursor = start; if (tag < 128) { // Single-byte field tag, we can use a slightly quicker count. @@ -929,7 +740,7 @@ jspb.utils.countFixedFields_ = function(buffer, start, end, tag, stride) { } else { while (cursor < end) { // Skip the field tag, or exit if we find a non-matching tag. - var temp = tag; + let temp = tag; while (temp > 128) { if (buffer[cursor++] != ((temp & 0x7F) | 0x80)) return count; temp >>= 7; @@ -944,7 +755,7 @@ jspb.utils.countFixedFields_ = function(buffer, start, end, tag, stride) { } } return count; -}; +} /** @@ -955,12 +766,11 @@ jspb.utils.countFixedFields_ = function(buffer, start, end, tag, stride) { * @param {number} end The end point in the buffer to scan. * @param {number} field The field number to count. * @return {number} The number of matching fields in the buffer. - * @export */ -jspb.utils.countFixed32Fields = function(buffer, start, end, field) { - var tag = field * 8 + jspb.BinaryConstants.WireType.FIXED32; - return jspb.utils.countFixedFields_(buffer, start, end, tag, 4); -}; +function countFixed32Fields(buffer, start, end, field) { + const tag = field * 8 + BinaryConstants.WireType.FIXED32; + return countFixedFields_(buffer, start, end, tag, 4); +} /** @@ -971,12 +781,11 @@ jspb.utils.countFixed32Fields = function(buffer, start, end, field) { * @param {number} end The end point in the buffer to scan. * @param {number} field The field number to count * @return {number} The number of matching fields in the buffer. - * @export */ -jspb.utils.countFixed64Fields = function(buffer, start, end, field) { - var tag = field * 8 + jspb.BinaryConstants.WireType.FIXED64; - return jspb.utils.countFixedFields_(buffer, start, end, tag, 8); -}; +function countFixed64Fields(buffer, start, end, field) { + const tag = field * 8 + BinaryConstants.WireType.FIXED64; + return countFixedFields_(buffer, start, end, tag, 8); +} /** @@ -987,16 +796,15 @@ jspb.utils.countFixed64Fields = function(buffer, start, end, field) { * @param {number} end The end point in the buffer to scan. * @param {number} field The field number to count. * @return {number} The number of matching fields in the buffer. - * @export */ -jspb.utils.countDelimitedFields = function(buffer, start, end, field) { - var count = 0; - var cursor = start; - var tag = field * 8 + jspb.BinaryConstants.WireType.DELIMITED; +function countDelimitedFields(buffer, start, end, field) { + let count = 0; + let cursor = start; + const tag = field * 8 + BinaryConstants.WireType.DELIMITED; while (cursor < end) { // Skip the field tag, or exit if we find a non-matching tag. - var temp = tag; + let temp = tag; while (temp > 128) { if (buffer[cursor++] != ((temp & 0x7F) | 0x80)) return count; temp >>= 7; @@ -1007,8 +815,8 @@ jspb.utils.countDelimitedFields = function(buffer, start, end, field) { count++; // Decode the length prefix. - var length = 0; - var shift = 1; + let length = 0; + let shift = 1; while (1) { temp = buffer[cursor++]; length += (temp & 0x7f) * shift; @@ -1020,77 +828,19 @@ jspb.utils.countDelimitedFields = function(buffer, start, end, field) { cursor += length; } return count; -}; - - -/** - * 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. - * @return {string} Stringified bytes for text format. - * @export - */ -jspb.utils.debugBytesToTextFormat = function(byteSource) { - var s = '"'; - if (byteSource) { - var bytes = jspb.utils.byteSourceToUint8Array(byteSource); - for (var i = 0; i < bytes.length; i++) { - s += '\\x'; - if (bytes[i] < 16) s += '0'; - s += bytes[i].toString(16); - } - } - return s + '"'; -}; - - -/** - * String-ify a scalar for text format. Should be optimized away in non-debug. - * @param {string|number|boolean} scalar The scalar to stringify. - * @return {string} Stringified scalar for text format. - * @export - */ -jspb.utils.debugScalarToTextFormat = function(scalar) { - if (typeof scalar === 'string') { - return goog.string.quote(scalar); - } else { - return scalar.toString(); - } -}; - - -/** - * Utility function: convert a string with codepoints 0--255 inclusive to a - * Uint8Array. If any codepoints greater than 255 exist in the string, throws an - * exception. - * @param {string} str - * @return {!Uint8Array} - * @export - */ -jspb.utils.stringToByteArray = function(str) { - var arr = new Uint8Array(str.length); - for (var i = 0; i < str.length; i++) { - var codepoint = str.charCodeAt(i); - if (codepoint > 255) { - throw new Error( - 'Conversion error: string contains codepoint ' + - 'outside of byte range'); - } - arr[i] = codepoint; - } - return arr; -}; - +} /** - * Converts any type defined in jspb.ByteSource into a Uint8Array. - * @param {!jspb.ByteSource} data + * Converts any type defined in ByteSource into a Uint8Array. + * @param {!ByteSource|!ByteString} data + * @param {boolean=} copyByteString whether to make a copy of ByteString + * internal data * @return {!Uint8Array} * @suppress {invalidCasts} - * @export */ -jspb.utils.byteSourceToUint8Array = function(data) { +function byteSourceToUint8Array(data, copyByteString) { + // Comparing a property is much faster than the instanceof test below, so + // prefer this. if (data.constructor === Uint8Array) { return /** @type {!Uint8Array} */ (data); } @@ -1107,18 +857,95 @@ jspb.utils.byteSourceToUint8Array = function(data) { if (data.constructor === String) { data = /** @type {string} */ (data); - return goog.crypt.base64.decodeStringToUint8Array(data); + return decodeStringToUint8Array(data); + } + + if (data.constructor === ByteString) { + data = /** @type {!ByteString} */ (data); + if (!copyByteString) { + return unsafeUint8ArrayFromByteString(data); + } + return data.asUint8Array(); } if (data instanceof Uint8Array) { - // Support types like nodejs Buffer and other subclasses of Uint8Array. + // Support types like nodejs Buffer (a subclass of Uint8Array). data = /** @type {!Uint8Array} */ (data); - // Make a shallow copy to ensure we only ever deal with Uint8Array + // Make a shallow copy to ensure jspb code only ever deals with Uint8Array // exactly to ensure monomorphism. return /** @type {!Uint8Array} */ ( - new Uint8Array(data.buffer, data.byteOffset, data.byteLength)); + new Uint8Array(data.buffer, data.byteOffset, data.byteLength)); } + throw new Error( + 'Type not convertible to a Uint8Array, expected a Uint8Array, an ' + + 'ArrayBuffer, a base64 encoded string, or Array of numbers'); +} + +/** @return {number} */ +function getSplit64Low() { + return split64Low; +} +/** @return {number} */ +function getSplit64High() { + return split64High; +} + +/** + * Makes a wire tag + * @return {number} + */ +function makeTag( + /** number */ fieldNumber, + /** !BinaryConstants.WireType */ wireType) { + // N.B. can't use << 3 because that enforces 2s complement 32 bit math and we + // want unsigned math. Consider that `(2**29 -1) << 3 === -8` but + // `(2**29 -1) * 8 === 4294967288` + return fieldNumber * 8 + wireType; +} - jspb.asserts.fail('Type not convertible to Uint8Array.'); - return /** @type {!Uint8Array} */ (new Uint8Array(0)); +/** @const {number} */ +const LOW_16_BITS = 0xFFFF; + +/** @const {number} */ +const LOW_24_BITS = 0xFFFFFF; + +/** @const {number} */ +const ALL_32_BITS = 0xFFFFFFFF; + +/** @const {number} String(Number.MAX_SAFE_INTEGER).length */ +const MAX_SAFE_INTEGER_DECIMAL_LENGTH = 16; + +exports = { + byteSourceToUint8Array, + countDelimitedFields, + countFixed32Fields, + countFixed64Fields, + countVarintFields, + countVarints, + fromZigzag32, + fromZigzag64, + getSplit64High, + getSplit64Low, + joinFloat32, + joinFloat64, + joinInt64, + joinNegativeDecimalStringFallback, + joinSignedDecimalString, + joinSignedNumberOrDecimalString, + joinUint64, + joinUnsignedDecimalString, + joinUnsignedDecimalStringFallback, + joinUnsignedNumberOrDecimalString, + joinZigzag64, + makeTag, + sliceUint8Array, + splitDecimalString, + splitDecimalStringFallback, + splitFloat32, + splitFloat64, + splitInt64, + splitUint64, + splitZigzag64, + toZigzag32, + toZigzag64, }; diff --git a/binary/utils_test.js b/binary/utils_test.js old mode 100644 new mode 100755 index 5a46905..9f0624b --- a/binary/utils_test.js +++ b/binary/utils_test.js @@ -42,6 +42,35 @@ goog.require('jspb.BinaryConstants'); goog.require('jspb.BinaryWriter'); goog.require('jspb.utils'); +const BinaryWriter = goog.module.get('jspb.BinaryWriter'); +const BinaryMessage = goog.module.get('jspb.BinaryConstants').BinaryMessage; +const FLOAT32_EPS = goog.module.get('jspb.BinaryConstants').FLOAT32_EPS; +const FLOAT32_MAX = goog.module.get('jspb.BinaryConstants').FLOAT32_MAX; +const FLOAT32_MIN = goog.module.get('jspb.BinaryConstants').FLOAT32_MIN; +const FLOAT64_EPS = goog.module.get('jspb.BinaryConstants').FLOAT64_EPS; +const FLOAT64_MAX = goog.module.get('jspb.BinaryConstants').FLOAT64_MAX; +const FLOAT64_MIN = goog.module.get('jspb.BinaryConstants').FLOAT64_MIN; +const byteArrayToString = goog.module.get('goog.crypt').byteArrayToString; +const encodeByteArray = goog.module.get('goog.crypt.base64').encodeByteArray; +const byteSourceToUint8Array = goog.module.get('jspb.utils').byteSourceToUint8Array; +const countDelimitedFields = goog.module.get('jspb.utils').countDelimitedFields; +const countFixed32Fields = goog.module.get('jspb.utils').countFixed32Fields; +const countFixed64Fields = goog.module.get('jspb.utils').countFixed64Fields; +const countVarintFields = goog.module.get('jspb.utils').countVarintFields; +const countVarints = goog.module.get('jspb.utils').countVarints; +const fromZigzag64 = goog.module.get('jspb.utils').fromZigzag64; +const getSplit64High = goog.module.get('jspb.utils').getSplit64High; +const getSplit64Low = goog.module.get('jspb.utils').getSplit64Low; +const hash64ArrayToDecimalStrings = goog.module.get('jspb.utils').hash64ArrayToDecimalStrings; +const hexStringToHash64 = goog.module.get('jspb.utils').hexStringToHash64; +const joinFloat32 = goog.module.get('jspb.utils').joinFloat32; +const joinFloat64 = goog.module.get('jspb.utils').joinFloat64; +const joinUnsignedDecimalString = goog.module.get('jspb.utils').joinUnsignedDecimalString; +const splitDecimalString = goog.module.get('jspb.utils').splitDecimalString; +const splitFloat32 = goog.module.get('jspb.utils').splitFloat32; +const splitFloat64 = goog.module.get('jspb.utils').splitFloat64; +const toZigzag64 = goog.module.get('jspb.utils').toZigzag64; + /** * @param {number} x @@ -63,9 +92,9 @@ function truncate(x) { */ function toHashString(bitsLow, bitsHigh) { return String.fromCharCode( - (bitsLow >>> 0) & 0xFF, (bitsLow >>> 8) & 0xFF, (bitsLow >>> 16) & 0xFF, - (bitsLow >>> 24) & 0xFF, (bitsHigh >>> 0) & 0xFF, (bitsHigh >>> 8) & 0xFF, - (bitsHigh >>> 16) & 0xFF, (bitsHigh >>> 24) & 0xFF); + (bitsLow >>> 0) & 0xFF, (bitsLow >>> 8) & 0xFF, (bitsLow >>> 16) & 0xFF, + (bitsLow >>> 24) & 0xFF, (bitsHigh >>> 0) & 0xFF, (bitsHigh >>> 8) & 0xFF, + (bitsHigh >>> 16) & 0xFF, (bitsHigh >>> 24) & 0xFF); } @@ -75,36 +104,36 @@ describe('binaryUtilsTest', () => { */ it('testDecimalConversion', () => { // Check some magic numbers. - let result = jspb.utils.joinUnsignedDecimalString(0x89e80001, 0x8ac72304); + let result = joinUnsignedDecimalString(0x89e80001, 0x8ac72304); expect(result).toEqual('10000000000000000001'); - result = jspb.utils.joinUnsignedDecimalString(0xacd05f15, 0x1b69b4b); + result = joinUnsignedDecimalString(0xacd05f15, 0x1b69b4b); expect(result).toEqual('123456789123456789'); - result = jspb.utils.joinUnsignedDecimalString(0xeb1f0ad2, 0xab54a98c); + result = joinUnsignedDecimalString(0xeb1f0ad2, 0xab54a98c); expect(result).toEqual('12345678901234567890'); - result = jspb.utils.joinUnsignedDecimalString(0xe3b70cb1, 0x891087b8); + result = joinUnsignedDecimalString(0xe3b70cb1, 0x891087b8); expect(result).toEqual('9876543210987654321'); // Check limits. - result = jspb.utils.joinUnsignedDecimalString(0x00000000, 0x00000000); + result = joinUnsignedDecimalString(0x00000000, 0x00000000); expect(result).toEqual('0'); - result = jspb.utils.joinUnsignedDecimalString(0xFFFFFFFF, 0xFFFFFFFF); + result = joinUnsignedDecimalString(0xFFFFFFFF, 0xFFFFFFFF); expect(result).toEqual('18446744073709551615'); // Check each bit of the low dword. for (let i = 0; i < 32; i++) { const low = (1 << i) >>> 0; - result = jspb.utils.joinUnsignedDecimalString(low, 0); + result = joinUnsignedDecimalString(low, 0); expect(result).toEqual('' + Math.pow(2, i)); } // Check the first 20 bits of the high dword. for (let i = 0; i < 20; i++) { const high = (1 << i) >>> 0; - result = jspb.utils.joinUnsignedDecimalString(0, high); + result = joinUnsignedDecimalString(0, high); expect(result).toEqual('' + Math.pow(2, 32 + i)); } @@ -112,201 +141,43 @@ describe('binaryUtilsTest', () => { // 2^52, even if they're representable integers - check the rest of the bits // manually against the correct string representations of 2^N. - result = jspb.utils.joinUnsignedDecimalString(0x00000000, 0x00100000); + result = joinUnsignedDecimalString(0x00000000, 0x00100000); expect(result).toEqual('4503599627370496'); - result = jspb.utils.joinUnsignedDecimalString(0x00000000, 0x00200000); + result = joinUnsignedDecimalString(0x00000000, 0x00200000); expect(result).toEqual('9007199254740992'); - result = jspb.utils.joinUnsignedDecimalString(0x00000000, 0x00400000); + result = joinUnsignedDecimalString(0x00000000, 0x00400000); expect(result).toEqual('18014398509481984'); - result = jspb.utils.joinUnsignedDecimalString(0x00000000, 0x00800000); + result = joinUnsignedDecimalString(0x00000000, 0x00800000); expect(result).toEqual('36028797018963968'); - result = jspb.utils.joinUnsignedDecimalString(0x00000000, 0x01000000); + result = joinUnsignedDecimalString(0x00000000, 0x01000000); expect(result).toEqual('72057594037927936'); - result = jspb.utils.joinUnsignedDecimalString(0x00000000, 0x02000000); + result = joinUnsignedDecimalString(0x00000000, 0x02000000); expect(result).toEqual('144115188075855872'); - result = jspb.utils.joinUnsignedDecimalString(0x00000000, 0x04000000); + result = joinUnsignedDecimalString(0x00000000, 0x04000000); expect(result).toEqual('288230376151711744'); - result = jspb.utils.joinUnsignedDecimalString(0x00000000, 0x08000000); + result = joinUnsignedDecimalString(0x00000000, 0x08000000); expect(result).toEqual('576460752303423488'); - result = jspb.utils.joinUnsignedDecimalString(0x00000000, 0x10000000); + result = joinUnsignedDecimalString(0x00000000, 0x10000000); expect(result).toEqual('1152921504606846976'); - result = jspb.utils.joinUnsignedDecimalString(0x00000000, 0x20000000); + result = joinUnsignedDecimalString(0x00000000, 0x20000000); expect(result).toEqual('2305843009213693952'); - result = jspb.utils.joinUnsignedDecimalString(0x00000000, 0x40000000); + result = joinUnsignedDecimalString(0x00000000, 0x40000000); expect(result).toEqual('4611686018427387904'); - result = jspb.utils.joinUnsignedDecimalString(0x00000000, 0x80000000); - expect(result).toEqual('9223372036854775808'); - }); - - - /** - * Going from hash strings to decimal strings should also be lossless. - */ - it('testHashToDecimalConversion', () => { - let result; - const convert = jspb.utils.hash64ToDecimalString; - - result = convert(toHashString(0x00000000, 0x00000000), false); - expect(result).toEqual('0'); - - result = convert(toHashString(0x00000000, 0x00000000), true); - expect(result).toEqual('0'); - - result = convert(toHashString(0xFFFFFFFF, 0xFFFFFFFF), false); - expect(result).toEqual('18446744073709551615'); - - result = convert(toHashString(0xFFFFFFFF, 0xFFFFFFFF), true); - expect(result).toEqual('-1'); - - result = convert(toHashString(0x00000000, 0x80000000), false); + result = joinUnsignedDecimalString(0x00000000, 0x80000000); expect(result).toEqual('9223372036854775808'); - - result = convert(toHashString(0x00000000, 0x80000000), true); - expect(result).toEqual('-9223372036854775808'); - - result = convert(toHashString(0xacd05f15, 0x01b69b4b), false); - expect(result).toEqual('123456789123456789'); - - result = convert(toHashString(~0xacd05f15 + 1, ~0x01b69b4b), true); - expect(result).toEqual('-123456789123456789'); - - // And converting arrays of hashes should work the same way. - result = jspb.utils.hash64ArrayToDecimalStrings( - [ - toHashString(0xFFFFFFFF, 0xFFFFFFFF), - toHashString(0x00000000, 0x80000000), - toHashString(0xacd05f15, 0x01b69b4b) - ], - false); - expect(result.length).toEqual(3); - expect(result[0]).toEqual('18446744073709551615'); - expect(result[1]).toEqual('9223372036854775808'); - expect(result[2]).toEqual('123456789123456789'); - }); - - /* - * Going from decimal strings to hash strings should be lossless. - */ - it('testDecimalToHashConversion', () => { - let result; - const convert = jspb.utils.decimalStringToHash64; - - result = convert('0'); - expect(result).toEqual(goog.crypt.byteArrayToString( - [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])); - - result = convert('-1'); - expect(result).toEqual(goog.crypt.byteArrayToString( - [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])); - - result = convert('18446744073709551615'); - expect(result).toEqual(goog.crypt.byteArrayToString( - [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])); - - result = convert('9223372036854775808'); - expect(result).toEqual(goog.crypt.byteArrayToString( - [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80])); - - result = convert('-9223372036854775808'); - expect(result).toEqual(goog.crypt.byteArrayToString( - [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80])); - - result = convert('123456789123456789'); - expect(result).toEqual(goog.crypt.byteArrayToString( - [0x15, 0x5F, 0xD0, 0xAC, 0x4B, 0x9B, 0xB6, 0x01])); - - result = convert('-123456789123456789'); - expect(result).toEqual(goog.crypt.byteArrayToString( - [0xEB, 0xA0, 0x2F, 0x53, 0xB4, 0x64, 0x49, 0xFE])); - }); - - /** - * Going from hash strings to hex strings should be lossless. - */ - it('testHashToHexConversion', () => { - let result; - const convert = jspb.utils.hash64ToHexString; - - result = convert(toHashString(0x00000000, 0x00000000)); - expect(result).toEqual('0x0000000000000000'); - - result = convert(toHashString(0xFFFFFFFF, 0xFFFFFFFF)); - expect(result).toEqual('0xffffffffffffffff'); - - result = convert(toHashString(0x12345678, 0x9ABCDEF0)); - expect(result).toEqual('0x9abcdef012345678'); - }); - - - /** - * Going from hex strings to hash strings should be lossless. - */ - it('testHexToHashConversion', () => { - let result; - const convert = jspb.utils.hexStringToHash64; - - result = convert('0x0000000000000000'); - expect(result).toEqual(goog.crypt.byteArrayToString( - [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])); - - result = convert('0xffffffffffffffff'); - expect(result).toEqual(goog.crypt.byteArrayToString( - [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])); - - // Hex string is big-endian, hash string is little-endian. - result = convert('0x123456789ABCDEF0'); - expect(result).toEqual(goog.crypt.byteArrayToString( - [0xF0, 0xDE, 0xBC, 0x9A, 0x78, 0x56, 0x34, 0x12])); - - // Capitalization should not matter. - result = convert('0x0000abcdefABCDEF'); - expect(result).toEqual(goog.crypt.byteArrayToString( - [0xEF, 0xCD, 0xAB, 0xEF, 0xCD, 0xAB, 0x00, 0x00])); - }); - - - /** - * Going from numbers to hash strings should be lossless for up to 53 bits of - * precision. - */ - it('testNumberToHashConversion', () => { - let result; - const convert = jspb.utils.numberToHash64; - - result = convert(0x0000000000000); - expect(jspb.utils.hash64ToHexString(result)).toEqual('0x0000000000000000'); - - result = convert(0xFFFFFFFFFFFFF); - expect(jspb.utils.hash64ToHexString(result)).toEqual('0x000fffffffffffff'); - - result = convert(0x123456789ABCD); - expect(jspb.utils.hash64ToHexString(result)).toEqual('0x000123456789abcd'); - - result = convert(0xDCBA987654321); - expect(jspb.utils.hash64ToHexString(result)).toEqual('0x000dcba987654321'); - - // 53 bits of precision should not be truncated. - result = convert(0x10000000000001); - expect(jspb.utils.hash64ToHexString(result)).toEqual('0x0010000000000001'); - - // 54 bits of precision should be truncated. - result = convert(0x20000000000001); - expect(jspb.utils.hash64ToHexString(result)) - .not.toEqual('0x0020000000000001'); }); - /** * Sanity check the behavior of Javascript's strings when doing funny things * with unicode characters. @@ -336,30 +207,24 @@ describe('binaryUtilsTest', () => { * Tests conversion from 32-bit floating point numbers to split64 numbers. */ it('testFloat32ToSplit64', () => { - const f32_eps = jspb.BinaryConstants.FLOAT32_EPS; - const f32_min = jspb.BinaryConstants.FLOAT32_MIN; - const f32_max = jspb.BinaryConstants.FLOAT32_MAX; - const f32_max_safe_int = jspb.utils.joinFloat32(0x4b7fffff, 0); + const f32_max_safe_int = joinFloat32(0x4b7fffff, 0); const f32_pi = Math.fround(Math.PI); // NaN. - jspb.utils.splitFloat32(NaN); - expect(isNaN(jspb.utils.joinFloat32( - jspb.utils.getSplit64Low(), jspb.utils.getSplit64High()))) - .toEqual(true); + splitFloat32(NaN); + expect(isNaN(joinFloat32(getSplit64Low(), getSplit64High()))).toEqual(true); /** * @param {number} x * @param {number=} opt_bits */ function test(x, opt_bits) { - jspb.utils.splitFloat32(x); + splitFloat32(x); if (opt_bits !== undefined) { - if (opt_bits != jspb.utils.getSplit64Low()) throw 'fail!'; + if (opt_bits != getSplit64Low()) throw 'fail!'; } expect(truncate(x)) - .toEqual(jspb.utils.joinFloat32( - jspb.utils.getSplit64Low(), jspb.utils.getSplit64High())); + .toEqual(joinFloat32(getSplit64Low(), getSplit64High())); } // Positive and negative infinity. @@ -371,16 +236,16 @@ describe('binaryUtilsTest', () => { test(-0, 0x80000000); // Positive and negative epsilon. - test(f32_eps, 0x00000001); - test(-f32_eps, 0x80000001); + test(FLOAT32_EPS, 0x00000001); + test(-FLOAT32_EPS, 0x80000001); // Positive and negative min. - test(f32_min, 0x00800000); - test(-f32_min, 0x80800000); + test(FLOAT32_MIN, 0x00800000); + test(-FLOAT32_MIN, 0x80800000); // Positive and negative max. - test(f32_max, 0x7F7FFFFF); - test(-f32_max, 0xFF7FFFFF); + test(FLOAT32_MAX, 0x7F7FFFFF); + test(-FLOAT32_MAX, 0xFF7FFFFF); // Positive and negative max_safe_int. test(f32_max_safe_int, 0x4B7FFFFF); @@ -395,14 +260,14 @@ describe('binaryUtilsTest', () => { test(Math.sin(30 * Math.PI / 180), 0x3f000000); // sin(30 degrees) // Various positive values. - let cursor = f32_eps * 10; + let cursor = FLOAT32_EPS * 10; while (cursor != Infinity) { test(cursor); cursor *= 1.1; } // Various negative values. - cursor = -f32_eps * 10; + cursor = -FLOAT32_EPS * 10; while (cursor != -Infinity) { test(cursor); cursor *= 1.1; @@ -414,15 +279,9 @@ describe('binaryUtilsTest', () => { * Tests conversion from 64-bit floating point numbers to split64 numbers. */ it('testFloat64ToSplit64', () => { - const f64_eps = jspb.BinaryConstants.FLOAT64_EPS; - const f64_min = jspb.BinaryConstants.FLOAT64_MIN; - const f64_max = jspb.BinaryConstants.FLOAT64_MAX; - // NaN. - jspb.utils.splitFloat64(NaN); - expect(isNaN(jspb.utils.joinFloat64( - jspb.utils.split64Low, jspb.utils.split64High))) - .toEqual(true); + splitFloat64(NaN); + expect(isNaN(joinFloat64(getSplit64Low(), getSplit64High()))).toEqual(true); /** * @param {number} x @@ -430,18 +289,16 @@ describe('binaryUtilsTest', () => { * @param {number=} opt_lowBits */ function test(x, opt_highBits, opt_lowBits) { - jspb.utils.splitFloat64(x); + splitFloat64(x); if (opt_highBits !== undefined) { - const split64High = jspb.utils.getSplit64High(); + const split64High = getSplit64High(); expect(opt_highBits.toString(16)).toEqual(split64High.toString(16)); } if (opt_lowBits !== undefined) { - const split64Low = jspb.utils.getSplit64Low(); + const split64Low = getSplit64Low(); expect(opt_lowBits.toString(16)).toEqual(split64Low.toString(16)); } - expect( - jspb.utils.joinFloat64(jspb.utils.getSplit64Low(), jspb.utils.getSplit64High())) - .toEqual(x); + expect(joinFloat64(getSplit64Low(), getSplit64High())).toEqual(x); } // Positive and negative infinity. @@ -456,16 +313,16 @@ describe('binaryUtilsTest', () => { test(2, 0x40000000, 0x00000000); // Positive and negative epsilon. - test(f64_eps, 0x00000000, 0x00000001); - test(-f64_eps, 0x80000000, 0x00000001); + test(FLOAT64_EPS, 0x00000000, 0x00000001); + test(-FLOAT64_EPS, 0x80000000, 0x00000001); // Positive and negative min. - test(f64_min, 0x00100000, 0x00000000); - test(-f64_min, 0x80100000, 0x00000000); + test(FLOAT64_MIN, 0x00100000, 0x00000000); + test(-FLOAT64_MIN, 0x80100000, 0x00000000); // Positive and negative max. - test(f64_max, 0x7FEFFFFF, 0xFFFFFFFF); - test(-f64_max, 0xFFEFFFFF, 0xFFFFFFFF); + test(FLOAT64_MAX, 0x7FEFFFFF, 0xFFFFFFFF); + test(-FLOAT64_MAX, 0xFFEFFFFF, 0xFFFFFFFF); test(Number.MAX_SAFE_INTEGER, 0x433FFFFF, 0xFFFFFFFF); test(Number.MIN_SAFE_INTEGER, 0xC33FFFFF, 0xFFFFFFFF); @@ -478,17 +335,17 @@ describe('binaryUtilsTest', () => { test(1.9999999999999998, 0x3FFFFFFF, 0xFFFFFFFF); test(2.225073858507201e-308, 0x000FFFFF, 0xFFFFFFFF); test(Math.PI, 0x400921fb, 0x54442d18); - test(jspb.BinaryConstants.FLOAT32_MIN, 0x38100000, 0x00000000); + test(FLOAT32_MIN, 0x38100000, 0x00000000); // Various positive values. - let cursor = f64_eps * 10; + let cursor = FLOAT64_EPS * 10; while (cursor != Infinity) { test(cursor); cursor *= 1.1; } // Various negative values. - cursor = -f64_eps * 10; + cursor = -FLOAT64_EPS * 10; while (cursor != -Infinity) { test(cursor); cursor *= 1.1; @@ -500,22 +357,19 @@ describe('binaryUtilsTest', () => { */ it('can encode and decode zigzag 64', () => { function stringToHiLoPair(str) { - jspb.utils.splitDecimalString(str); - return { - lo: jspb.utils.getSplit64Low() >>> 0, - hi: jspb.utils.getSplit64High() >>> 0 - }; + splitDecimalString(str); + return { lo: getSplit64Low() >>> 0, hi: getSplit64High() >>> 0 }; } function makeHiLoPair(lo, hi) { - return {lo: lo >>> 0, hi: hi >>> 0}; + return { lo: lo >>> 0, hi: hi >>> 0 }; } // Test cases directly from the protobuf dev guide. // https://engdoc.corp.google.com/eng/howto/protocolbuffers/developerguide/encoding.shtml?cl=head#types const 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('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') @@ -535,10 +389,10 @@ describe('binaryUtilsTest', () => { }, ]; 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); + expect(toZigzag64(c.original.lo, c.original.hi, makeHiLoPair)) + .toEqual(c.zigzag); + expect(fromZigzag64(c.zigzag.lo, c.zigzag.hi, makeHiLoPair)) + .toEqual(c.original); } }); @@ -552,15 +406,14 @@ describe('binaryUtilsTest', () => { values.push(Math.floor(i)); } - const writer = new jspb.BinaryWriter(); + const writer = new BinaryWriter(); writer.writePackedUint64(1, values); const buffer = new Uint8Array(writer.getResultBuffer()); // We should have two more varints than we started with - one for the field // tag, one for the packed length. - expect(jspb.utils.countVarints(buffer, 0, buffer.length)) - .toEqual(values.length + 2); + expect(countVarints(buffer, 0, buffer.length)).toEqual(values.length + 2); }); @@ -568,7 +421,7 @@ describe('binaryUtilsTest', () => { * Tests counting matching varint fields. */ it('testCountVarintFields', () => { - let writer = new jspb.BinaryWriter(); + let writer = new BinaryWriter(); let count = 0; for (let i = 1; i < 1000000000; i *= 1.1) { @@ -578,10 +431,9 @@ describe('binaryUtilsTest', () => { writer.writeString(2, 'terminator'); let buffer = new Uint8Array(writer.getResultBuffer()); - expect(jspb.utils.countVarintFields(buffer, 0, buffer.length, 1)) - .toEqual(count); + expect(countVarintFields(buffer, 0, buffer.length, 1)).toEqual(count); - writer = new jspb.BinaryWriter(); + writer = new BinaryWriter(); count = 0; for (let i = 1; i < 1000000000; i *= 1.1) { @@ -591,8 +443,7 @@ describe('binaryUtilsTest', () => { writer.writeString(2, 'terminator'); buffer = new Uint8Array(writer.getResultBuffer()); - expect(jspb.utils.countVarintFields(buffer, 0, buffer.length, 123456789)) - .toEqual(count); + expect(countVarintFields(buffer, 0, buffer.length, 123456789)).toEqual(count); }); @@ -600,7 +451,7 @@ describe('binaryUtilsTest', () => { * Tests counting matching fixed32 fields. */ it('testCountFixed32Fields', () => { - let writer = new jspb.BinaryWriter(); + let writer = new BinaryWriter(); let count = 0; for (let i = 1; i < 1000000000; i *= 1.1) { @@ -610,10 +461,9 @@ describe('binaryUtilsTest', () => { writer.writeString(2, 'terminator'); let buffer = new Uint8Array(writer.getResultBuffer()); - expect(jspb.utils.countFixed32Fields(buffer, 0, buffer.length, 1)) - .toEqual(count); + expect(countFixed32Fields(buffer, 0, buffer.length, 1)).toEqual(count); - writer = new jspb.BinaryWriter(); + writer = new BinaryWriter(); count = 0; for (let i = 1; i < 1000000000; i *= 1.1) { @@ -623,8 +473,7 @@ describe('binaryUtilsTest', () => { writer.writeString(2, 'terminator'); buffer = new Uint8Array(writer.getResultBuffer()); - expect(jspb.utils.countFixed32Fields(buffer, 0, buffer.length, 123456789)) - .toEqual(count); + expect(countFixed32Fields(buffer, 0, buffer.length, 123456789)).toEqual(count); }); @@ -632,7 +481,7 @@ describe('binaryUtilsTest', () => { * Tests counting matching fixed64 fields. */ it('testCountFixed64Fields', () => { - let writer = new jspb.BinaryWriter(); + let writer = new BinaryWriter(); let count = 0; for (let i = 1; i < 1000000000; i *= 1.1) { @@ -642,10 +491,9 @@ describe('binaryUtilsTest', () => { writer.writeString(2, 'terminator'); let buffer = new Uint8Array(writer.getResultBuffer()); - expect(jspb.utils.countFixed64Fields(buffer, 0, buffer.length, 1)) - .toEqual(count); + expect(countFixed64Fields(buffer, 0, buffer.length, 1)).toEqual(count); - writer = new jspb.BinaryWriter(); + writer = new BinaryWriter(); count = 0; for (let i = 1; i < 1000000000; i *= 1.1) { @@ -655,8 +503,7 @@ describe('binaryUtilsTest', () => { writer.writeString(2, 'terminator'); buffer = new Uint8Array(writer.getResultBuffer()); - expect(jspb.utils.countFixed64Fields(buffer, 0, buffer.length, 123456789)) - .toEqual(count); + expect(countFixed64Fields(buffer, 0, buffer.length, 123456789)).toEqual(count); }); @@ -664,7 +511,7 @@ describe('binaryUtilsTest', () => { * Tests counting matching delimited fields. */ it('testCountDelimitedFields', () => { - let writer = new jspb.BinaryWriter(); + let writer = new BinaryWriter(); let count = 0; for (let i = 1; i < 1000; i *= 1.1) { @@ -674,10 +521,9 @@ describe('binaryUtilsTest', () => { writer.writeString(2, 'terminator'); let buffer = new Uint8Array(writer.getResultBuffer()); - expect(jspb.utils.countDelimitedFields(buffer, 0, buffer.length, 1)) - .toEqual(count); + expect(countDelimitedFields(buffer, 0, buffer.length, 1)).toEqual(count); - writer = new jspb.BinaryWriter(); + writer = new BinaryWriter(); count = 0; for (let i = 1; i < 1000; i *= 1.1) { @@ -687,27 +533,15 @@ describe('binaryUtilsTest', () => { writer.writeString(2, 'terminator'); buffer = new Uint8Array(writer.getResultBuffer()); - expect(jspb.utils.countDelimitedFields(buffer, 0, buffer.length, 123456789)) - .toEqual(count); + expect(countDelimitedFields(buffer, 0, buffer.length, 123456789)) + .toEqual(count); }); - - /** - * Tests byte format for debug strings. - */ - it('testDebugBytesToTextFormat', () => { - expect(jspb.utils.debugBytesToTextFormat(null)).toEqual('""'); - expect(jspb.utils.debugBytesToTextFormat([ - 0, 16, 255 - ])).toEqual('"\\x00\\x10\\xff"'); - }); - - /** * Tests converting byte blob sources into byte blobs. */ it('testByteSourceToUint8Array', () => { - const convert = jspb.utils.byteSourceToUint8Array; + const convert = byteSourceToUint8Array; const sourceData = []; for (let i = 0; i < 256; i++) { @@ -716,7 +550,7 @@ describe('binaryUtilsTest', () => { const sourceBytes = new Uint8Array(sourceData); const sourceBuffer = sourceBytes.buffer; - const sourceBase64 = goog.crypt.base64.encodeByteArray(sourceData); + const sourceBase64 = encodeByteArray(sourceData); function check(result) { expect(result.constructor).toEqual(Uint8Array); diff --git a/binary/writer.js b/binary/writer.js old mode 100644 new mode 100755 index c1876b1..e3874de --- a/binary/writer.js +++ b/binary/writer.js @@ -1,33 +1,3 @@ -// Protocol Buffers - Google's data interchange format -// Copyright 2008 Google Inc. All rights reserved. -// https://protobuf.dev/ -// -// 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 This file contains utilities for encoding Javascript objects * into binary, wire-format protocol buffers (in the form of Uint8Arrays) that @@ -52,1887 +22,1698 @@ * Major caveat 3 - This class uses typed arrays and must not be used on older * browsers that do not support them. * - * @suppress {missingRequire} TODO(b/152540451): this shouldn't be needed * @author aappleby@google.com (Austin Appleby) */ -goog.provide('jspb.BinaryWriter'); - -goog.require('goog.crypt.base64'); - -goog.require('jspb.asserts'); -goog.require('jspb.BinaryConstants'); -goog.require('jspb.BinaryEncoder'); -goog.require('jspb.arith.Int64'); -goog.require('jspb.arith.UInt64'); -goog.require('jspb.utils'); - - +goog.module('jspb.binary.writer'); +goog.module.declareLegacyNamespace(); + +const { Alphabet, encodeByteArray: encodeByteArraySlow } = goog.require('goog.crypt.base64'); +const { AnyFieldType } = goog.requireType('jspb.binary.any_field_type'); +const { BinaryEncoder } = goog.require('jspb.binary.encoder'); +const { ByteSource } = goog.requireType('jspb.binary.bytesource'); +const { ByteString } = goog.require('jspb.bytestring'); +const { FieldType, TWO_TO_31, TWO_TO_32, TWO_TO_63, TWO_TO_64, WireType } = goog.require('jspb.BinaryConstants'); +const { Int64, UInt64 } = goog.require('jspb.arith'); +const { assert, fail } = goog.require('goog.asserts'); +const { bufferFromSource } = goog.require('jspb.binary.internal_buffer'); +const { encodeByteArray } = goog.require('jspb.internal_bytes'); +const { encodeUtf8 } = goog.require('jspb.binary.utf8'); +const { makeTag } = goog.require('jspb.utils'); +const { unsafeByteStringFromUint8Array, unsafeUint8ArrayFromByteString } = goog.require('jspb.unsafe_bytestring'); +/** + * Whether to reject unpaired surrogates when encoding strings to utf8. + * + *

Currently set to `goog.DEBUG`, but can be disabled if needed. + * + * @define {boolean} + */ +const REJECT_UNPAIRED_SURROGATES = + goog.define('jspb.binary.REJECT_UNPAIRED_SURROGATES', goog.DEBUG); /** * BinaryWriter implements encoders for all the wire types specified in - * https://protobuf.dev/programming-guides/encoding/. - * - * @constructor - * @struct - * @export - */ -jspb.BinaryWriter = function() { - /** - * Blocks of serialized data that will be concatenated once all messages have - * been written. - * @private {!Array>} - */ - this.blocks_ = []; + * https://developers.google.com/protocol-buffers/docs/encoding. + */ +class BinaryWriter { + constructor() { + /** + * Blocks of serialized data that will be concatenated once all messages + * have been written. + * @private {!Array>} + */ + this.blocks_ = []; + + /** + * Total number of bytes in the blocks_ array. Does _not_ include bytes in + * the encoder below. + * @private {number} + */ + this.totalLength_ = 0; + + /** + * 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 @const {!BinaryEncoder} + */ + this.encoder_ = new BinaryEncoder(); + } + + /** @private*/ + pushBlock(/** !Array|!Uint8Array */ buffer) { + // Repeated calls to appendUint8Arrays may produce empty arrays from the + // encoder, avoid storing these in our list of blocks. + if (buffer.length !== 0) { + this.blocks_.push(buffer); + this.totalLength_ += buffer.length; + } + } /** - * Total number of bytes in the blocks_ array. Does _not_ include bytes in - * the encoder below. - * @private {number} + * Append a typed array of bytes onto the buffer. + * + * @param {!Uint8Array} arr The byte array to append. + * @private */ - this.totalLength_ = 0; + appendUint8Array_(arr) { + this.pushBlock(this.encoder_.end()); + this.pushBlock(arr); + } /** - * 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} + * 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 {!Array} + * @private */ - this.encoder_ = new jspb.BinaryEncoder(); + beginDelimited_(field) { + this.writeFieldHeader_(field, WireType.DELIMITED); + const bookmark = this.encoder_.end(); + this.pushBlock(bookmark); + bookmark.push( + this.totalLength_); // store the current length in the bookmark + return bookmark; + } /** - * 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>} + * 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} bookmark + * @private */ - this.bookmarks_ = []; -}; - - -/** - * Append a typed array of bytes onto the buffer. - * - * @param {!Uint8Array} arr The byte array to append. - * @private - */ -jspb.BinaryWriter.prototype.appendUint8Array_ = function(arr) { - var temp = this.encoder_.end(); - this.blocks_.push(temp); - this.blocks_.push(arr); - this.totalLength_ += temp.length + arr.length; -}; - - -/** - * 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 {!Array} - * @private - */ -jspb.BinaryWriter.prototype.beginDelimited_ = function(field) { - 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 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} bookmark - * @private - */ -jspb.BinaryWriter.prototype.endDelimited_ = function(bookmark) { - var oldLength = bookmark.pop(); - var messageLength = this.totalLength_ + this.encoder_.length() - oldLength; - jspb.asserts.assert(messageLength >= 0); - - while (messageLength > 127) { - bookmark.push((messageLength & 0x7f) | 0x80); - messageLength = messageLength >>> 7; + endDelimited_(bookmark) { + const oldLength = bookmark.pop(); + let messageLength = this.totalLength_ + this.encoder_.length() - oldLength; + assert(messageLength >= 0); + + while (messageLength > 127) { + bookmark.push((messageLength & 0x7f) | 0x80); + messageLength = messageLength >>> 7; + this.totalLength_++; + } + + bookmark.push(messageLength); this.totalLength_++; } - 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. - * @export - */ -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. - * @export - */ -jspb.BinaryWriter.prototype.maybeWriteSerializedMessage = function( - bytes, start, end) { - if (bytes != null && start != null && end != null) { - this.writeSerializedMessage(bytes, start, end); + /** + * Writes unknown fields to the output if there are any. + */ + writeUnknownFields(/** !Array */ unknownFields) { + this.pushBlock(this.encoder_.end()); + for (let i = 0; i < unknownFields.length; i++) { + // Unsafe access is ok here since arrays added to our output are always + // copied before returning to the user. + this.pushBlock(unsafeUint8ArrayFromByteString(unknownFields[i])); + } } -}; - - -/** - * Resets the writer, throwing away any accumulated buffers. - * @export - */ -jspb.BinaryWriter.prototype.reset = function() { - this.blocks_ = []; - this.encoder_.end(); - this.totalLength_ = 0; - this.bookmarks_ = []; -}; - - -/** - * Converts the encoded data into a Uint8Array. - * @return {!Uint8Array} - * @export - */ -jspb.BinaryWriter.prototype.getResultBuffer = function() { - jspb.asserts.assert(this.bookmarks_.length == 0); - - var flat = new Uint8Array(this.totalLength_ + this.encoder_.length()); - var blocks = this.blocks_; - var blockCount = blocks.length; - var offset = 0; - - for (var i = 0; i < blockCount; i++) { - var block = blocks[i]; - flat.set(block, offset); - offset += block.length; + /** + * 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. + */ + writeSerializedMessage(bytes, start, end) { + this.appendUint8Array_(bytes.subarray(start, end)); } - var tail = this.encoder_.end(); - flat.set(tail, offset); - offset += tail.length; - - // Post condition: `flattened` must have had every byte written. - jspb.asserts.assert(offset == flat.length); - - // Replace our block list with the flattened block, which lets GC reclaim - // the temp blocks sooner. - this.blocks_ = [flat]; - - return flat; -}; - - -/** - * Converts the encoded data into a base64-encoded string. - * @param {!goog.crypt.base64.Alphabet=} alphabet Which flavor of base64 to use. - * @return {string} - * @export - */ -jspb.BinaryWriter.prototype.getResultBase64String = function(alphabet) { - return goog.crypt.base64.encodeByteArray(this.getResultBuffer(), alphabet); -}; - - -/** - * Begins a new sub-message. The client must call endSubMessage() when they're - * done. - * TODO(aappleby): Deprecated. Move callers to writeMessage(). - * @param {number} field The field number of the sub-message. - * @export - */ -jspb.BinaryWriter.prototype.beginSubMessage = function(field) { - this.bookmarks_.push(this.beginDelimited_(field)); -}; - - -/** - * Finishes a sub-message and packs it into the parent messages' buffer. - * TODO(aappleby): Deprecated. Move callers to writeMessage(). - * @export - */ -jspb.BinaryWriter.prototype.endSubMessage = function() { - jspb.asserts.assert(this.bookmarks_.length >= 0); - this.endDelimited_(this.bookmarks_.pop()); -}; - - -/** - * 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. - * @param {number} wireType The wire-type of the field, as specified in the - * protocol buffer documentation. - * @private - */ -jspb.BinaryWriter.prototype.writeFieldHeader_ = function(field, wireType) { - jspb.asserts.assert(field >= 1 && field == Math.floor(field)); - var x = field * 8 + wireType; - this.encoder_.writeUnsignedVarint32(x); -}; - - -/** - * Writes a field of any valid scalar type to the binary stream. - * @param {jspb.BinaryConstants.FieldType} fieldType - * @param {number} field - * @param {jspb.AnyFieldType} value - * @export - */ -jspb.BinaryWriter.prototype.writeAny = function(fieldType, field, value) { - var fieldTypes = jspb.BinaryConstants.FieldType; - switch (fieldType) { - case fieldTypes.DOUBLE: - this.writeDouble(field, /** @type {number} */ (value)); - return; - case fieldTypes.FLOAT: - this.writeFloat(field, /** @type {number} */ (value)); - return; - case fieldTypes.INT64: - this.writeInt64(field, /** @type {number} */ (value)); - return; - case fieldTypes.UINT64: - this.writeUint64(field, /** @type {number} */ (value)); - return; - case fieldTypes.INT32: - this.writeInt32(field, /** @type {number} */ (value)); - return; - case fieldTypes.FIXED64: - this.writeFixed64(field, /** @type {number} */ (value)); - return; - case fieldTypes.FIXED32: - this.writeFixed32(field, /** @type {number} */ (value)); - return; - case fieldTypes.BOOL: - this.writeBool(field, /** @type {boolean} */ (value)); - return; - case fieldTypes.STRING: - this.writeString(field, /** @type {string} */ (value)); - return; - case fieldTypes.GROUP: - jspb.asserts.fail('Group field type not supported in writeAny()'); - return; - case fieldTypes.MESSAGE: - jspb.asserts.fail('Message field type not supported in writeAny()'); - return; - case fieldTypes.BYTES: - this.writeBytes(field, /** @type {?Uint8Array} */ (value)); - return; - case fieldTypes.UINT32: - this.writeUint32(field, /** @type {number} */ (value)); - return; - case fieldTypes.ENUM: - this.writeEnum(field, /** @type {number} */ (value)); - return; - case fieldTypes.SFIXED32: - this.writeSfixed32(field, /** @type {number} */ (value)); - return; - case fieldTypes.SFIXED64: - this.writeSfixed64(field, /** @type {number} */ (value)); - return; - case fieldTypes.SINT32: - this.writeSint32(field, /** @type {number} */ (value)); - return; - case fieldTypes.SINT64: - this.writeSint64(field, /** @type {number} */ (value)); - return; - case fieldTypes.FHASH64: - this.writeFixedHash64(field, /** @type {string} */ (value)); - return; - case fieldTypes.VHASH64: - this.writeVarintHash64(field, /** @type {string} */ (value)); - return; - default: - jspb.asserts.fail('Invalid field type in writeAny()'); - return; - } -}; - - -/** - * 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.writeUnsignedVarint32_ = function(field, value) { - if (value == null) return; - this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.VARINT); - this.encoder_.writeUnsignedVarint32(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.writeSignedVarint32_ = function(field, value) { - if (value == null) return; - 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); -}; - - -/** - * 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.writeSignedVarint64_ = function(field, value) { - if (value == null) return; - this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.VARINT); - this.encoder_.writeSignedVarint64(value); -}; - - -/** - * Writes a zigzag 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.writeZigzagVarint32_ = function(field, value) { - if (value == null) return; - this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.VARINT); - this.encoder_.writeZigzagVarint32(value); -}; - - -/** - * Writes a zigzag 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.writeZigzagVarint64_ = function(field, value) { - if (value == null) return; - this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.VARINT); - this.encoder_.writeZigzagVarint64(value); -}; - - -/** - * 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.writeZigzagVarint64String_ = function( - field, value) { - if (value == null) return; - this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.VARINT); - this.encoder_.writeZigzagVarint64String(value); -}; - - -/** - * 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. - * @param {number?} value The value to write. - * @export - */ -jspb.BinaryWriter.prototype.writeInt32 = function(field, value) { - if (value == null) return; - jspb.asserts.assert( - (value >= -jspb.BinaryConstants.TWO_TO_31) && - (value < jspb.BinaryConstants.TWO_TO_31)); - this.writeSignedVarint32_(field, value); -}; - - -/** - * Writes an int32 field represented as a string to the buffer. Numbers outside - * the range [-2^31,2^31) will be truncated. - * @param {number} field The field number. - * @param {string?} value The value to write. - * @export - */ -jspb.BinaryWriter.prototype.writeInt32String = function(field, value) { - if (value == null) return; - var intValue = /** {number} */ parseInt(value, 10); - jspb.asserts.assert( - (intValue >= -jspb.BinaryConstants.TWO_TO_31) && - (intValue < jspb.BinaryConstants.TWO_TO_31)); - this.writeSignedVarint32_(field, intValue); -}; - - -/** - * Writes an int64 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. - * @export - */ -jspb.BinaryWriter.prototype.writeInt64 = function(field, value) { - if (value == null) return; - jspb.asserts.assert( - (value >= -jspb.BinaryConstants.TWO_TO_63) && - (value < jspb.BinaryConstants.TWO_TO_63)); - this.writeSignedVarint64_(field, value); -}; - - -/** - * Writes a int64 field (with value as a string) to the buffer. - * @param {number} field The field number. - * @param {string?} value The value to write. - * @export - */ -jspb.BinaryWriter.prototype.writeInt64String = function(field, value) { - if (value == null) return; - var num = jspb.arith.Int64.fromString(value); - this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.VARINT); - this.encoder_.writeSplitVarint64(num.lo, num.hi); -}; - - -/** - * Writes a uint32 field to the buffer. Numbers outside the range [0,2^32) - * will be truncated. - * @param {number} field The field number. - * @param {number?} value The value to write. - * @export - */ -jspb.BinaryWriter.prototype.writeUint32 = function(field, value) { - if (value == null) return; - jspb.asserts.assert( - (value >= 0) && (value < jspb.BinaryConstants.TWO_TO_32)); - this.writeUnsignedVarint32_(field, value); -}; - - -/** - * Writes a uint32 field represented as a string to the buffer. Numbers outside - * the range [0,2^32) will be truncated. - * @param {number} field The field number. - * @param {string?} value The value to write. - * @export - */ -jspb.BinaryWriter.prototype.writeUint32String = function(field, value) { - if (value == null) return; - var intValue = /** {number} */ parseInt(value, 10); - jspb.asserts.assert( - (intValue >= 0) && (intValue < jspb.BinaryConstants.TWO_TO_32)); - this.writeUnsignedVarint32_(field, intValue); -}; - - -/** - * Writes a uint64 field to the buffer. Numbers outside the range [0,2^64) - * will be truncated. - * @param {number} field The field number. - * @param {number?} value The value to write. - * @export - */ -jspb.BinaryWriter.prototype.writeUint64 = function(field, value) { - if (value == null) return; - jspb.asserts.assert( - (value >= 0) && (value < jspb.BinaryConstants.TWO_TO_64)); - this.writeUnsignedVarint64_(field, value); -}; - - -/** - * Writes a uint64 field (with value as a string) to the buffer. - * @param {number} field The field number. - * @param {string?} value The value to write. - * @export - */ -jspb.BinaryWriter.prototype.writeUint64String = function(field, value) { - if (value == null) return; - var num = jspb.arith.UInt64.fromString(value); - this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.VARINT); - this.encoder_.writeSplitVarint64(num.lo, num.hi); -}; - - -/** - * 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. - * @export - */ -jspb.BinaryWriter.prototype.writeSint32 = function(field, value) { - if (value == null) return; - jspb.asserts.assert( - (value >= -jspb.BinaryConstants.TWO_TO_31) && - (value < jspb.BinaryConstants.TWO_TO_31)); - this.writeZigzagVarint32_(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 {number?} value The value to write. - * @export - */ -jspb.BinaryWriter.prototype.writeSint64 = function(field, value) { - if (value == null) return; - jspb.asserts.assert( - (value >= -jspb.BinaryConstants.TWO_TO_63) && - (value < jspb.BinaryConstants.TWO_TO_63)); - this.writeZigzagVarint64_(field, value); -}; - - -/** - * 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. - * @export - */ -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. - * @export - */ -jspb.BinaryWriter.prototype.writeSint64String = function(field, value) { - if (value == null) return; - this.writeZigzagVarint64String_(field, value); -}; - - -/** - * Writes a fixed32 field to the buffer. Numbers outside the range [0,2^32) - * will be truncated. - * @param {number} field The field number. - * @param {number?} value The value to write. - * @export - */ -jspb.BinaryWriter.prototype.writeFixed32 = function(field, value) { - if (value == null) return; - jspb.asserts.assert( - (value >= 0) && (value < jspb.BinaryConstants.TWO_TO_32)); - this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.FIXED32); - this.encoder_.writeUint32(value); -}; - - -/** - * Writes a fixed64 field to the buffer. Numbers outside the range [0,2^64) - * will be truncated. - * @param {number} field The field number. - * @param {number?} value The value to write. - * @export - */ -jspb.BinaryWriter.prototype.writeFixed64 = function(field, value) { - if (value == null) return; - jspb.asserts.assert( - (value >= 0) && (value < jspb.BinaryConstants.TWO_TO_64)); - this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.FIXED64); - this.encoder_.writeUint64(value); -}; - - -/** - * Writes a fixed64 field (with value as a string) to the buffer. - * @param {number} field The field number. - * @param {string?} value The value to write. - * @export - */ -jspb.BinaryWriter.prototype.writeFixed64String = function(field, value) { - if (value == null) return; - var num = jspb.arith.UInt64.fromString(value); - this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.FIXED64); - this.encoder_.writeSplitFixed64(num.lo, num.hi); -}; - - -/** - * Writes a sfixed32 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. - * @export - */ -jspb.BinaryWriter.prototype.writeSfixed32 = function(field, value) { - if (value == null) return; - jspb.asserts.assert( - (value >= -jspb.BinaryConstants.TWO_TO_31) && - (value < jspb.BinaryConstants.TWO_TO_31)); - this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.FIXED32); - this.encoder_.writeInt32(value); -}; - - -/** - * Writes a sfixed64 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. - * @export - */ -jspb.BinaryWriter.prototype.writeSfixed64 = function(field, value) { - if (value == null) return; - jspb.asserts.assert( - (value >= -jspb.BinaryConstants.TWO_TO_63) && - (value < jspb.BinaryConstants.TWO_TO_63)); - this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.FIXED64); - this.encoder_.writeInt64(value); -}; - - -/** - * Writes a sfixed64 string 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 value to write. - * @export - */ -jspb.BinaryWriter.prototype.writeSfixed64String = function(field, value) { - if (value == null) return; - var num = jspb.arith.Int64.fromString(value); - this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.FIXED64); - this.encoder_.writeSplitFixed64(num.lo, num.hi); -}; - - -/** - * Writes a single-precision floating point field to the buffer. Numbers - * requiring more than 32 bits of precision will be truncated. - * @param {number} field The field number. - * @param {number?} value The value to write. - * @export - */ -jspb.BinaryWriter.prototype.writeFloat = function(field, value) { - if (value == null) return; - this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.FIXED32); - this.encoder_.writeFloat(value); -}; - - -/** - * Writes a double-precision floating point field to the buffer. As this is the - * native format used by JavaScript, no precision will be lost. - * @param {number} field The field number. - * @param {number?} value The value to write. - * @export - */ -jspb.BinaryWriter.prototype.writeDouble = function(field, value) { - if (value == null) return; - this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.FIXED64); - this.encoder_.writeDouble(value); -}; - - -/** - * Writes a boolean field to the buffer. We allow numbers as input - * because the JSPB code generator uses 0/1 instead of true/false to save space - * in the string representation of the proto. - * @param {number} field The field number. - * @param {boolean?|number?} value The value to write. - * @export - */ -jspb.BinaryWriter.prototype.writeBool = function(field, value) { - if (value == null) return; - jspb.asserts.assert( - typeof value === 'boolean' || typeof value === 'number'); - this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.VARINT); - this.encoder_.writeBool(value); -}; - - -/** - * Writes an enum field to the buffer. - * @param {number} field The field number. - * @param {number?} value The value to write. - * @export - */ -jspb.BinaryWriter.prototype.writeEnum = function(field, value) { - if (value == null) return; - jspb.asserts.assert( - (value >= -jspb.BinaryConstants.TWO_TO_31) && - (value < jspb.BinaryConstants.TWO_TO_31)); - this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.VARINT); - this.encoder_.writeSignedVarint32(value); -}; - - -/** - * Writes a string field to the buffer. - * @param {number} field The field number. - * @param {string?} value The string to write. - * @export - */ -jspb.BinaryWriter.prototype.writeString = function(field, value) { - if (value == null) return; - 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. - * @param {number} field The field number. - * @param {?jspb.ByteSource} value The array of bytes to write. - * @export - */ -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. - * @param {number} field The field number. - * @param {?MessageType} value The message to write. - * @param {function(MessageTypeNonNull, !jspb.BinaryWriter)} writerCallback - * Will be invoked with the value to write and the writer to write it with. - * @template MessageType - * Use go/closure-ttl to declare a non-nullable version of MessageType. Replace - * the null in blah|null with none. This is necessary because the compiler will - * infer MessageType to be nullable if the value parameter is nullable. - * @template MessageTypeNonNull := - * cond(isUnknown(MessageType), unknown(), - * mapunion(MessageType, (X) => - * cond(eq(X, 'null'), none(), X))) - * =: - * @export - */ -jspb.BinaryWriter.prototype.writeMessage = function( - field, value, writerCallback) { - if (value == null) return; - var bookmark = this.beginDelimited_(field); - writerCallback(value, this); - this.endDelimited_(bookmark); -}; - - -/** - * Writes a message set extension to the buffer. - * @param {number} field The field number for the extension. - * @param {?MessageType} value The extension message object to write. Note that - * message set can only have extensions with type of optional message. - * @param {function(!MessageTypeNonNull, !jspb.BinaryWriter)} writerCallback - * Will be invoked with the value to write and the writer to write it with. - * @template MessageType - * Use go/closure-ttl to declare a non-nullable version of MessageType. Replace - * the null in blah|null with none. This is necessary because the compiler will - * infer MessageType to be nullable if the value parameter is nullable. - * @template MessageTypeNonNull := - * cond(isUnknown(MessageType), unknown(), - * mapunion(MessageType, (X) => - * cond(eq(X, 'null'), none(), X))) - * =: - * @export - */ -jspb.BinaryWriter.prototype.writeMessageSet = function( - field, value, writerCallback) { - if (value == null) return; - // The wire format for a message set is defined by - // google3/net/proto/message_set.proto - this.writeFieldHeader_(1, jspb.BinaryConstants.WireType.START_GROUP); - this.writeFieldHeader_(2, jspb.BinaryConstants.WireType.VARINT); - this.encoder_.writeSignedVarint32(field); - var bookmark = this.beginDelimited_(3); - writerCallback(value, this); - this.endDelimited_(bookmark); - this.writeFieldHeader_(1, jspb.BinaryConstants.WireType.END_GROUP); -}; - - -/** - * Writes a group message to the buffer. - * - * @param {number} field The field number. - * @param {?MessageType} value The message to write, wrapped with START_GROUP / - * END_GROUP tags. Will be a no-op if 'value' is null. - * @param {function(MessageTypeNonNull, !jspb.BinaryWriter)} writerCallback - * Will be invoked with the value to write and the writer to write it with. - * @template MessageType - * Use go/closure-ttl to declare a non-nullable version of MessageType. Replace - * the null in blah|null with none. This is necessary because the compiler will - * infer MessageType to be nullable if the value parameter is nullable. - * @template MessageTypeNonNull := - * cond(isUnknown(MessageType), unknown(), - * mapunion(MessageType, (X) => - * cond(eq(X, 'null'), none(), X))) - * =: - * @export - */ -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); -}; - - -/** - * Writes a 64-bit hash string field (8 characters @ 8 bits of data each) to - * the buffer. - * @param {number} field The field number. - * @param {string?} value The hash string. - * @export - */ -jspb.BinaryWriter.prototype.writeFixedHash64 = function(field, value) { - if (value == null) return; - jspb.asserts.assert(value.length == 8); - this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.FIXED64); - this.encoder_.writeFixedHash64(value); -}; - - -/** - * Writes a 64-bit hash string field (8 characters @ 8 bits of data each) to - * the buffer. - * @param {number} field The field number. - * @param {string?} value The hash string. - * @export - */ -jspb.BinaryWriter.prototype.writeVarintHash64 = function(field, value) { - if (value == null) return; - jspb.asserts.assert(value.length == 8); - this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.VARINT); - this.encoder_.writeVarintHash64(value); -}; - - -/** - * Writes a 64-bit field to the buffer as a fixed64. - * @param {number} field The field number. - * @param {number} lowBits The low 32 bits. - * @param {number} highBits The high 32 bits. - * @export - */ -jspb.BinaryWriter.prototype.writeSplitFixed64 = function( - field, lowBits, highBits) { - this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.FIXED64); - this.encoder_.writeSplitFixed64(lowBits, highBits); -}; - - -/** - * Writes a 64-bit field to the buffer as a varint. - * @param {number} field The field number. - * @param {number} lowBits The low 32 bits. - * @param {number} highBits The high 32 bits. - * @export - */ -jspb.BinaryWriter.prototype.writeSplitVarint64 = function( - field, lowBits, highBits) { - this.writeFieldHeader_(field, jspb.BinaryConstants.WireType.VARINT); - this.encoder_.writeSplitVarint64(lowBits, highBits); -}; - - -/** - * 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. - * @export - */ -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} value The array of ints to write. - * @export - */ -jspb.BinaryWriter.prototype.writeRepeatedInt32 = function(field, value) { - if (value == null) return; - for (var i = 0; i < value.length; i++) { - this.writeSignedVarint32_(field, value[i]); + /** + * 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. + */ + maybeWriteSerializedMessage(bytes, start, end) { + if (bytes != null && start != null && end != null) { + this.writeSerializedMessage(bytes, start, end); + } } -}; - -/** - * Writes an array of numbers formatted as strings to the buffer as a repeated - * 32-bit int field. - * @param {number} field The field number. - * @param {?Array} value The array of ints to write. - * @export - */ -jspb.BinaryWriter.prototype.writeRepeatedInt32String = function(field, value) { - if (value == null) return; - for (var i = 0; i < value.length; i++) { - this.writeInt32String(field, value[i]); + /** + * Resets the writer, throwing away any accumulated buffers. + */ + reset() { + this.blocks_ = []; + this.encoder_.end(); + this.totalLength_ = 0; } -}; - -/** - * Writes an array of numbers to the buffer as a repeated 64-bit int field. - * @param {number} field The field number. - * @param {?Array} value The array of ints to write. - * @export - */ -jspb.BinaryWriter.prototype.writeRepeatedInt64 = function(field, value) { - if (value == null) return; - for (var i = 0; i < value.length; i++) { - this.writeSignedVarint64_(field, value[i]); + /** + * Converts the encoded data into a Uint8Array. + * @return {!Uint8Array} + */ + getResultBuffer() { + // flush the encoder to avoid a special case below. + this.pushBlock(this.encoder_.end()); + const resultLength = this.totalLength_; + // NOTE: some of the Uint8Arrays stored in blocks_ are backing stores for + // ByteString objects and so we should be careful not to leak references to + // them from here. i.o.w. don't add an optimization that directly returns + // references to things in blocks. + const flat = new Uint8Array(resultLength); + const blocks = this.blocks_; + const blockCount = blocks.length; + let offset = 0; + + for (let i = 0; i < blockCount; i++) { + const block = blocks[i]; + flat.set(block, offset); + offset += block.length; + } + + // Post condition: `flattened` must have had every byte written. + assert(offset == flat.length); + + // Replace our block list with the flattened block, which lets GC reclaim + // the temp blocks sooner. + this.blocks_ = [flat]; + + return flat; } -}; - -/** - * Writes an array of 64-bit values to the buffer as a fixed64. - * @param {number} field The field number. - * @param {?Array} 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 - * @export - */ -jspb.BinaryWriter.prototype.writeRepeatedSplitFixed64 = function( - field, value, lo, hi) { - if (value == null) return; - for (var i = 0; i < value.length; i++) { - this.writeSplitFixed64(field, lo(value[i]), hi(value[i])); + /** + * Converts the encoded data into a ByteString. + * @return {!ByteString} + */ + getResultBufferAsByteString() { + // Note that this is safe because we never leak or mutate the Uint8Array + // returned by getResultBuffer even though it's stored in `blocks_`. + return unsafeByteStringFromUint8Array(this.getResultBuffer()); } -}; - -/** - * Writes an array of 64-bit values to the buffer as a varint. - * @param {number} field The field number. - * @param {?Array} 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 - * @export - */ -jspb.BinaryWriter.prototype.writeRepeatedSplitVarint64 = function( - field, value, lo, hi) { - if (value == null) return; - for (var i = 0; i < value.length; i++) { - this.writeSplitVarint64(field, lo(value[i]), hi(value[i])); + /** + * Converts the encoded data into a base64-encoded string. + * @param {!Alphabet=} alphabet Which flavor of base64 to + * use. + * @return {string} + */ + getResultBase64String(alphabet) { + if (alphabet === undefined) { + return encodeByteArray(this.getResultBuffer()); + } else { + return encodeByteArraySlow(this.getResultBuffer(), alphabet); + } } -}; - -/** - * Writes an array of 64-bit values to the buffer as a zigzag varint. - * @param {number} field The field number. - * @param {?Array} 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 - * @export - */ -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])); + /** + * 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. + * @param {!WireType} wireType The wire-type of the field, as specified in the + * protocol buffer documentation. + * @private + */ + writeFieldHeader_(field, wireType) { + assert(field >= 1 && field == Math.floor(field)); + this.encoder_.writeUnsignedVarint32(makeTag(field, wireType)); } -}; - -/** - * Writes an array of numbers formatted as strings to the buffer as a repeated - * 64-bit int field. - * @param {number} field The field number. - * @param {?Array} value The array of ints to write. - * @export - */ -jspb.BinaryWriter.prototype.writeRepeatedInt64String = function(field, value) { - if (value == null) return; - for (var i = 0; i < value.length; i++) { - this.writeInt64String(field, value[i]); + // TODO(b/221101646): Maybe update AnyFieldType to include ByteString. + /** + * Writes a field of any valid scalar type to the binary stream. + * @param {!FieldType} fieldType + * @param {number} field + * @param {!AnyFieldType|!ByteString} value + */ + writeAny(fieldType, field, value) { + switch (fieldType) { + case FieldType.DOUBLE: + this.writeDouble(field, /** @type {number} */(value)); + return; + case FieldType.FLOAT: + this.writeFloat(field, /** @type {number} */(value)); + return; + case FieldType.INT64: + this.writeInt64(field, /** @type {number} */(value)); + return; + case FieldType.UINT64: + this.writeUint64(field, /** @type {number} */(value)); + return; + case FieldType.INT32: + this.writeInt32(field, /** @type {number} */(value)); + return; + case FieldType.FIXED64: + this.writeFixed64(field, /** @type {number} */(value)); + return; + case FieldType.FIXED32: + this.writeFixed32(field, /** @type {number} */(value)); + return; + case FieldType.BOOL: + this.writeBool(field, /** @type {boolean} */(value)); + return; + case FieldType.STRING: + this.writeString(field, /** @type {string} */(value)); + return; + case FieldType.GROUP: + fail('Group field type not supported in writeAny()'); + return; + case FieldType.MESSAGE: + fail('Message field type not supported in writeAny()'); + return; + case FieldType.BYTES: + this.writeBytes(field, /** @type {?ByteSource|?ByteString} */(value)); + return; + case FieldType.UINT32: + this.writeUint32(field, /** @type {number} */(value)); + return; + case FieldType.ENUM: + this.writeEnum(field, /** @type {number} */(value)); + return; + case FieldType.SFIXED32: + this.writeSfixed32(field, /** @type {number} */(value)); + return; + case FieldType.SFIXED64: + this.writeSfixed64(field, /** @type {number} */(value)); + return; + case FieldType.SINT32: + this.writeSint32(field, /** @type {number} */(value)); + return; + case FieldType.SINT64: + this.writeSint64(field, /** @type {number} */(value)); + return; + default: + fail('Invalid field type in writeAny()'); + return; + } } -}; - -/** - * Writes an array numbers to the buffer as a repeated unsigned 32-bit int - * field. - * @param {number} field The field number. - * @param {?Array} value The array of ints to write. - * @export - */ -jspb.BinaryWriter.prototype.writeRepeatedUint32 = function(field, value) { - if (value == null) return; - for (var i = 0; i < value.length; i++) { - this.writeUnsignedVarint32_(field, value[i]); + /** + * Writes a varint field to the buffer without range checking. + * @param {number} field The field number. + * @param {number|null|undefined} value The value to write. + * @private + */ + writeUnsignedVarint32_(field, value) { + if (value == null) return; + this.writeFieldHeader_(field, WireType.VARINT); + this.encoder_.writeUnsignedVarint32(value); } -}; - -/** - * Writes an array of numbers formatted as strings to the buffer as a repeated - * unsigned 32-bit int field. - * @param {number} field The field number. - * @param {?Array} value The array of ints to write. - * @export - */ -jspb.BinaryWriter.prototype.writeRepeatedUint32String = function(field, value) { - if (value == null) return; - for (var i = 0; i < value.length; i++) { - this.writeUint32String(field, value[i]); + /** + * Writes a varint field to the buffer without range checking. + * @param {number} field The field number. + * @param {number|null|undefined} value The value to write. + * @private + */ + writeSignedVarint32_(field, value) { + if (value == null) return; + assertSignedInteger(field, value); + this.writeFieldHeader_(field, WireType.VARINT); + this.encoder_.writeSignedVarint32(value); } -}; - -/** - * Writes an array numbers to the buffer as a repeated unsigned 64-bit int - * field. - * @param {number} field The field number. - * @param {?Array} value The array of ints to write. - * @export - */ -jspb.BinaryWriter.prototype.writeRepeatedUint64 = function(field, value) { - if (value == null) return; - for (var i = 0; i < value.length; i++) { - this.writeUnsignedVarint64_(field, value[i]); + /** + * Writes a varint field to the buffer without range checking. + * @param {number} field The field number. + * @param {number|string|!bigint|null|undefined} value The value to write. + * @private + */ + writeUnsignedVarint64_(field, value) { + if (value == null) return; + this.writeFieldHeader_(field, WireType.VARINT); + switch (typeof value) { + case 'number': + this.encoder_.writeUnsignedVarint64(value); + break; + + case 'bigint': { + const num = UInt64.fromBigInt(/** @type {bigint} */(value)); + this.encoder_.writeSplitVarint64(num.lo, num.hi); + break; + } + + default: { + const num = UInt64.fromString(/** @type {string} */(value)); + this.encoder_.writeSplitVarint64(num.lo, num.hi); + break; + } + } } -}; - -/** - * Writes an array of numbers formatted as strings to the buffer as a repeated - * unsigned 64-bit int field. - * @param {number} field The field number. - * @param {?Array} value The array of ints to write. - * @export - */ -jspb.BinaryWriter.prototype.writeRepeatedUint64String = function(field, value) { - if (value == null) return; - for (var i = 0; i < value.length; i++) { - this.writeUint64String(field, value[i]); + /** + * Writes a varint field to the buffer without range checking. + * @param {number} field The field number. + * @param {number|string?|!bigint|null|undefined} value The value to write. + * @private + */ + writeSignedVarint64_(field, value) { + if (value == null) return; + this.writeFieldHeader_(field, WireType.VARINT); + switch (typeof value) { + case 'number': + this.encoder_.writeSignedVarint64(value); + break; + + case 'bigint': { + const num = Int64.fromBigInt(/** @type {bigint} */(value)); + this.encoder_.writeSplitVarint64(num.lo, num.hi); + break; + } + + default: { + const num = Int64.fromString(/** @type {string} */(value)); + this.encoder_.writeSplitVarint64(num.lo, num.hi); + break; + } + } } -}; - -/** - * Writes an array numbers to the buffer as a repeated signed 32-bit int field. - * @param {number} field The field number. - * @param {?Array} value The array of ints to write. - * @export - */ -jspb.BinaryWriter.prototype.writeRepeatedSint32 = function(field, value) { - if (value == null) return; - for (var i = 0; i < value.length; i++) { - this.writeZigzagVarint32_(field, value[i]); + /** + * Writes a zigzag varint field to the buffer without range checking. + * @param {number} field The field number. + * @param {number|null|undefined} value The value to write. + * @private + */ + writeZigzagVarint32_(field, value) { + if (value == null) return; + this.writeFieldHeader_(field, WireType.VARINT); + this.encoder_.writeZigzagVarint32(value); } -}; - -/** - * Writes an array numbers to the buffer as a repeated signed 64-bit int field. - * @param {number} field The field number. - * @param {?Array} value The array of ints to write. - * @export - */ -jspb.BinaryWriter.prototype.writeRepeatedSint64 = function(field, value) { - if (value == null) return; - for (var i = 0; i < value.length; i++) { - this.writeZigzagVarint64_(field, value[i]); + /** + * Writes a zigzag varint field to the buffer without range checking. + * @param {number} field The field number. + * @param {number|string|!bigint|null|undefined} value The value to write. + * @private + */ + writeZigzagVarint64_(field, value) { + if (value == null) return; + this.writeFieldHeader_(field, WireType.VARINT); + switch (typeof value) { + case 'number': + this.encoder_.writeZigzagVarint64(/** @type {number} */(value)); + break; + + case 'bigint': + this.encoder_.writeZigzagVarint64BigInt(/** @type {bigint} */(value)); + break; + + default: + this.encoder_.writeZigzagVarint64String(/** @type {string} */(value)); + break; + } } -}; - -/** - * Writes an array numbers to the buffer as a repeated signed 64-bit int field. - * @param {number} field The field number. - * @param {?Array} value The array of ints to write. - * @export - */ -jspb.BinaryWriter.prototype.writeRepeatedSint64String = function(field, value) { - if (value == null) return; - for (var i = 0; i < value.length; i++) { - this.writeZigzagVarint64String_(field, value[i]); + /** + * Writes an int32 field to the buffer. Numbers outside the range [-2^31,2^31) + * will be truncated. + * @param {number} field The field number. + * @param {number|null|undefined} value The value to write. + */ + writeInt32(field, value) { + if (value == null) return; + assertThat(field, value, (value >= -TWO_TO_31) && (value < TWO_TO_31)); + this.writeSignedVarint32_(field, value); } -}; - -/** - * 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} value The array of ints to write. - * @export - */ -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 int64 field to the buffer. Numbers outside the range [-2^63,2^63) + * will be truncated. + * @param {number} field The field number. + * @param {number|string|!bigint|null|undefined} value The value to write. + */ + writeInt64(field, value) { + if (value == null) return; + assertSignedInt64(field, value); + this.writeSignedVarint64_(field, value); } -}; + /** + * Writes a int64 field (with value as a string) to the buffer. + * @param {number} field The field number. + * @param {string|null|undefined} value The value to write. + * @deprecated Use writeInt64() + */ + writeInt64String(field, value) { + this.writeInt64(field, value); + } -/** - * 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. - * @param {?Array} value The array of ints to write. - * @export - */ -jspb.BinaryWriter.prototype.writeRepeatedFixed32 = function(field, value) { - if (value == null) return; - for (var i = 0; i < value.length; i++) { - this.writeFixed32(field, value[i]); + /** + * Writes a uint32 field to the buffer. Numbers outside the range [0,2^32) + * will be truncated. + * @param {number} field The field number. + * @param {number|null|undefined} value The value to write. + */ + writeUint32(field, value) { + if (value == null) return; + assertThat(field, value, (value >= 0) && (value < TWO_TO_32)); + this.writeUnsignedVarint32_(field, value); } -}; -/** - * Writes an array of numbers to the buffer as a repeated fixed64 field. This - * works for both signed and unsigned fixed64s. - * @param {number} field The field number. - * @param {?Array} value The array of ints to write. - * @export - */ -jspb.BinaryWriter.prototype.writeRepeatedFixed64 = function(field, value) { - if (value == null) return; - for (var i = 0; i < value.length; i++) { - this.writeFixed64(field, value[i]); + /** + * Writes a uint64 field to the buffer. Numbers outside the range [0,2^64) + * will be truncated. + * @param {number} field The field number. + * @param {number|string|!bigint|null|undefined} value The value to write. + */ + writeUint64(field, value) { + if (value == null) return; + assertUnsignedInt64(field, value); + this.writeUnsignedVarint64_(field, value); } -}; + /** + * Writes a uint64 field (with value as a string) to the buffer. + * @param {number} field The field number. + * @param {string|null|undefined} value The value to write. + * @deprecated Use writeUint64() + */ + writeUint64String(field, value) { + this.writeUint64(field, value); + } -/** - * Writes an array of numbers to the buffer as a repeated fixed64 field. This - * works for both signed and unsigned fixed64s. - * @param {number} field The field number. - * @param {?Array} value The array of decimal strings to write. - * @export - */ -jspb.BinaryWriter.prototype.writeRepeatedFixed64String = function( - field, value) { - if (value == null) return; - for (var i = 0; i < value.length; i++) { - this.writeFixed64String(field, value[i]); + /** + * 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|null|undefined} value The value to write. + */ + writeSint32(field, value) { + if (value == null) return; + assertThat(field, value, (value >= -TWO_TO_31) && (value < TWO_TO_31)); + this.writeZigzagVarint32_(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 {number|string|!bigint|null|undefined} value The value to write. + */ + writeSint64(field, value) { + if (value == null) return; + assertSignedInt64(field, value); + this.writeZigzagVarint64_(field, value); + } -/** - * Writes an array of numbers to the buffer as a repeated sfixed32 field. - * @param {number} field The field number. - * @param {?Array} value The array of ints to write. - * @export - */ -jspb.BinaryWriter.prototype.writeRepeatedSfixed32 = function(field, value) { - if (value == null) return; - for (var i = 0; i < value.length; i++) { - this.writeSfixed32(field, value[i]); + /** + * 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|null|undefined} value The decimal string to write. + * @deprecated Use writeSint64(); + */ + writeSint64String(field, value) { + this.writeSint64(field, value); } -}; + /** + * Writes a fixed32 field to the buffer. Numbers outside the range [0,2^32) + * will be truncated. + * @param {number} field The field number. + * @param {number|null|undefined} value The value to write. + */ + writeFixed32(field, value) { + if (value == null) return; + assertThat(field, value, (value >= 0) && (value < TWO_TO_32)); + this.writeFieldHeader_(field, WireType.FIXED32); + this.encoder_.writeUint32(value); + } -/** - * Writes an array of numbers to the buffer as a repeated sfixed64 field. - * @param {number} field The field number. - * @param {?Array} value The array of ints to write. - * @export - */ -jspb.BinaryWriter.prototype.writeRepeatedSfixed64 = function(field, value) { - if (value == null) return; - for (var i = 0; i < value.length; i++) { - this.writeSfixed64(field, value[i]); + /** + * Writes a fixed64 field to the buffer. Numbers outside the range [0,2^64) + * will be truncated. + * @param {number} field The field number. + * @param {number|string|!bigint|null|undefined} value The value to write. + */ + writeFixed64(field, value) { + if (value == null) return; + assertUnsignedInt64(field, value); + this.writeFieldHeader_(field, WireType.FIXED64); + switch (typeof value) { + case 'number': + this.encoder_.writeUint64(value); + break; + case 'bigint': { + const num = UInt64.fromBigInt(/** @type {bigint} */(value)); + this.encoder_.writeSplitFixed64(num.lo, num.hi); + break; + } + default: { + const num = UInt64.fromString(/** @type {string} */(value)); + this.encoder_.writeSplitFixed64(num.lo, num.hi); + break; + } + } } -}; + /** + * Writes a fixed64 field (with value as a string) to the buffer. + * @param {number} field The field number. + * @param {string|null|undefined} value The value to write. + * @deprecated Use writeFixed64(). + */ + writeFixed64String(field, value) { + this.writeFixed64(field, value); + } -/** - * Writes an array of decimal strings to the buffer as a repeated sfixed64 - * field. - * @param {number} field The field number. - * @param {?Array} value The array of decimal strings to write. - * @export - */ -jspb.BinaryWriter.prototype.writeRepeatedSfixed64String = function( - field, value) { - if (value == null) return; - for (var i = 0; i < value.length; i++) { - this.writeSfixed64String(field, value[i]); + /** + * Writes a sfixed32 field to the buffer. Numbers outside the range + * [-2^31,2^31) will be truncated. + * @param {number} field The field number. + * @param {number|null|undefined} value The value to write. + */ + writeSfixed32(field, value) { + if (value == null) return; + assertThat(field, value, (value >= -TWO_TO_31) && (value < TWO_TO_31)); + this.writeFieldHeader_(field, WireType.FIXED32); + this.encoder_.writeInt32(value); } -}; + /** + * Writes a sfixed64 field to the buffer. Numbers outside the range + * [-2^63,2^63) will be truncated. + * @param {number} field The field number. + * @param {number|string|!bigint|null|undefined} value The value to write. + */ + writeSfixed64(field, value) { + if (value == null) return; + assertSignedInt64(field, value); + this.writeFieldHeader_(field, WireType.FIXED64); + switch (typeof value) { + case 'number': + this.encoder_.writeInt64(value); + break; + case 'bigint': + const int64Big = Int64.fromBigInt(/** @type {bigint} */(value)); + this.encoder_.writeSplitFixed64(int64Big.lo, int64Big.hi); + break; + default: + const int64Str = Int64.fromString(/** @type {string} */(value)); + this.encoder_.writeSplitFixed64(int64Str.lo, int64Str.hi); + break; + } + } -/** - * Writes an array of numbers to the buffer as a repeated float field. - * @param {number} field The field number. - * @param {?Array} value The array of ints to write. - * @export - */ -jspb.BinaryWriter.prototype.writeRepeatedFloat = function(field, value) { - if (value == null) return; - for (var i = 0; i < value.length; i++) { - this.writeFloat(field, value[i]); + /** + * Writes a sfixed64 string field to the buffer. Numbers outside the range + * [-2^63,2^63) will be truncated. + * @param {number} field The field number. + * @param {string|null|undefined} value The value to write. + * @deprecated Use writeSfixed64(). + */ + writeSfixed64String(field, value) { + this.writeSfixed64(field, value); } -}; + /** + * Writes a single-precision floating point field to the buffer. Numbers + * requiring more than 32 bits of precision will be truncated. + * @param {number} field The field number. + * @param {number?|string|null|undefined} value The value to write, accepts + * 'Infinity'/'-Infinity'/'NaN' for JSPB wire format compatibility. + */ + writeFloat(field, value) { + if (value == null) return; + this.writeFieldHeader_(field, WireType.FIXED32); + this.encoder_.writeFloat(value); + } -/** - * Writes an array of numbers to the buffer as a repeated double field. - * @param {number} field The field number. - * @param {?Array} value The array of ints to write. - * @export - */ -jspb.BinaryWriter.prototype.writeRepeatedDouble = function(field, value) { - if (value == null) return; - for (var i = 0; i < value.length; i++) { - this.writeDouble(field, value[i]); + /** + * Writes a double-precision floating point field to the buffer. As this is + * the native format used by JavaScript, no precision will be lost. + * @param {number} field The field number. + * @param {number?|string|null|undefined} value The value to write, accepts + * 'Infinity'/'-Infinity'/'NaN' for JSPB wire format compatibility. + */ + writeDouble(field, value) { + if (value == null) return; + this.writeFieldHeader_(field, WireType.FIXED64); + this.encoder_.writeDouble(value); } -}; + /** + * Writes a boolean field to the buffer. We allow numbers as input + * because the JSPB code generator uses 0/1 instead of true/false to save + * space in the string representation of the proto. + * @param {number} field The field number. + * @param {boolean?|number|null|undefined} value The value to write. + */ + writeBool(field, value) { + if (value == null) return; + assertThat( + field, value, typeof value === 'boolean' || typeof value === 'number'); + this.writeFieldHeader_(field, WireType.VARINT); + this.encoder_.writeBool(value); + } -/** - * Writes an array of booleans to the buffer as a repeated bool field. - * @param {number} field The field number. - * @param {?Array} value The array of ints to write. - * @export - */ -jspb.BinaryWriter.prototype.writeRepeatedBool = function(field, value) { - if (value == null) return; - for (var i = 0; i < value.length; i++) { - this.writeBool(field, value[i]); + /** + * Writes an enum field to the buffer. + * @param {number} field The field number. + * @param {number|null|undefined} value The value to write. + */ + writeEnum(field, value) { + if (value == null) return; + // Converting since value might be object typed integer here. + const intValue = /** number */ parseInt(value, 10); + assertSignedInteger(field, intValue); + this.writeFieldHeader_(field, WireType.VARINT); + this.encoder_.writeSignedVarint32(intValue); } -}; + /** + * Writes a string field to the buffer. + * @param {number} field The field number. + * @param {string|null|undefined} value The string to write. + */ + writeString(field, value) { + if (value == null) return; + this.writeUint8Array( + field, + encodeUtf8( + value, /** rejectUnpairedSurrogates=*/ REJECT_UNPAIRED_SURROGATES)); + } -/** - * Writes an array of enums to the buffer as a repeated enum field. - * @param {number} field The field number. - * @param {?Array} value The array of ints to write. - * @export - */ -jspb.BinaryWriter.prototype.writeRepeatedEnum = function(field, value) { - if (value == null) return; - for (var i = 0; i < value.length; i++) { - this.writeEnum(field, value[i]); + /** + * Writes an arbitrary byte field to the buffer. Note - to match the behavior + * of the C++ implementation, empty byte arrays _are_ serialized. + * @param {number} field The field number. + * @param {?ByteSource|?ByteString|undefined} value The array of bytes to + * write. + */ + writeBytes(field, value) { + if (value == null) return; + this.writeUint8Array( + field, + bufferFromSource(value, /* treatNewDataAsImmutable= */ true).buffer); } -}; + /** + * @param {number} field The field number. + * @param {!Uint8Array} value The array of bytes to write. + * @private + */ + writeUint8Array(field, value) { + this.writeFieldHeader_(field, WireType.DELIMITED); + this.encoder_.writeUnsignedVarint32(value.length); + this.appendUint8Array_(value); + } -/** - * Writes an array of strings to the buffer as a repeated string field. - * @param {number} field The field number. - * @param {?Array} value The array of strings to write. - * @export - */ -jspb.BinaryWriter.prototype.writeRepeatedString = function(field, value) { - if (value == null) return; - for (var i = 0; i < value.length; i++) { - this.writeString(field, value[i]); + /** + * Writes a message to the buffer. + * @param {number} field The field number. + * @param {?MessageType|undefined} value The message to write. + * @param {function(MessageTypeNonNull, !BinaryWriter)} writerCallback + * Will be invoked with the value to write and the writer to write it + * with. + * @template MessageType + * Use go/closure-ttl to declare a non-nullable version of MessageType. + * Replace the null in blah|null with none. This is necessary because the + * compiler will infer MessageType to be nullable if the value parameter is + * nullable. + * @template MessageTypeNonNull := + * cond(isUnknown(MessageType), unknown(), + * mapunion(MessageType, (X) => + * cond(eq(X, 'undefined'), none(), cond(eq(X, 'null'), none(), X)))) + * =: + */ + writeMessage(field, value, writerCallback) { + if (value == null) return; + const bookmark = this.beginDelimited_(field); + writerCallback(value, this); + this.endDelimited_(bookmark); } -}; + /** + * Writes a message set extension to the buffer. + * @param {number} field The field number for the extension. + * @param {?MessageType|undefined} value The extension message object to + * write. Note that message set can only have extensions with type of + * optional message. + * @param {function(!MessageTypeNonNull, !BinaryWriter)} writerCallback + * Will be invoked with the value to write and the writer to write it + * with. + * @template MessageType + * Use go/closure-ttl to declare a non-nullable version of MessageType. + * Replace the null in blah|null with none. This is necessary because the + * compiler will infer MessageType to be nullable if the value parameter is + * nullable. + * @template MessageTypeNonNull := + * cond(isUnknown(MessageType), unknown(), + * mapunion(MessageType, (X) => + * cond(eq(X, 'undefined'), none(), cond(eq(X, 'null'), none(), X)))) + * =: + */ + writeMessageSet(field, value, writerCallback) { + if (value == null) return; + // The wire format for a message set is defined by + // google3/net/proto/message_set.proto + this.writeFieldHeader_(1, WireType.START_GROUP); + this.writeFieldHeader_(2, WireType.VARINT); + this.encoder_.writeSignedVarint32(field); + const bookmark = this.beginDelimited_(3); + writerCallback(value, this); + this.endDelimited_(bookmark); + this.writeFieldHeader_(1, WireType.END_GROUP); + } -/** - * Writes an array of arbitrary byte fields to the buffer. - * @param {number} field The field number. - * @param {?Array} value The arrays of arrays of bytes to - * write. - * @export - */ -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 a group message to the buffer. + * + * @param {number} field The field number. + * @param {?MessageType|undefined} value The message to write, wrapped with + * START_GROUP / END_GROUP tags. Will be a no-op if 'value' is null. + * @param {function(MessageTypeNonNull, !BinaryWriter)} writerCallback + * Will be invoked with the value to write and the writer to write it + * with. + * @template MessageType + * Use go/closure-ttl to declare a non-nullable version of MessageType. + * Replace the null in blah|null with none. This is necessary because the + * compiler will infer MessageType to be nullable if the value parameter is + * nullable. + * @template MessageTypeNonNull := + * cond(isUnknown(MessageType), unknown(), + * mapunion(MessageType, (X) => + * cond(eq(X, 'undefined'), none(), cond(eq(X, 'null'), none(), X)))) + * =: + */ + writeGroup(field, value, writerCallback) { + if (value == null) return; + this.writeFieldHeader_(field, WireType.START_GROUP); + writerCallback(value, this); + this.writeFieldHeader_(field, WireType.END_GROUP); } -}; + /** + * Writes a 64-bit field to the buffer as a fixed64. + * @param {number} field The field number. + * @param {number} lowBits The low 32 bits. + * @param {number} highBits The high 32 bits. + */ + writeSplitFixed64(field, lowBits, highBits) { + this.writeFieldHeader_(field, WireType.FIXED64); + this.encoder_.writeSplitFixed64(lowBits, highBits); + } -/** - * Writes an array of messages to the buffer. - * @template MessageType - * @param {number} field The field number. - * @param {?Array} value The array of messages to - * write. - * @param {function(MessageType, !jspb.BinaryWriter)} writerCallback - * Will be invoked with the value to write and the writer to write it with. - * @export - */ -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 a 64-bit field to the buffer as a varint. + * @param {number} field The field number. + * @param {number} lowBits The low 32 bits. + * @param {number} highBits The high 32 bits. + */ + writeSplitVarint64(field, lowBits, highBits) { + this.writeFieldHeader_(field, WireType.VARINT); + this.encoder_.writeSplitVarint64(lowBits, highBits); } -}; + /** + * 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. + */ + writeSplitZigzagVarint64(field, lowBits, highBits) { + this.writeFieldHeader_(field, WireType.VARINT); + this.encoder_.writeSplitZigzagVarint64(lowBits >>> 0, highBits >>> 0); + } -/** - * Writes an array of group messages to the buffer. - * @template MessageType - * @param {number} field The field number. - * @param {?Array} value The array of messages to - * write. - * @param {function(MessageType, !jspb.BinaryWriter)} writerCallback - * Will be invoked with the value to write and the writer to write it with. - * @export - */ -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); + /** + * Writes an array of numbers to the buffer as a repeated 32-bit int field. + * @param {number} field The field number. + * @param {?Array|undefined} value The array of ints to write. + */ + writeRepeatedInt32(field, value) { + if (value == null) return; + for (let i = 0; i < value.length; i++) { + this.writeSignedVarint32_(field, value[i]); + } } -}; -/** - * Writes a 64-bit hash string field (8 characters @ 8 bits of data each) to - * the buffer. - * @param {number} field The field number. - * @param {?Array} value The array of hashes to write. - * @export - */ -jspb.BinaryWriter.prototype.writeRepeatedFixedHash64 = function(field, value) { - if (value == null) return; - for (var i = 0; i < value.length; i++) { - this.writeFixedHash64(field, value[i]); + /** + * Writes an array of numbers to the buffer as a repeated 64-bit int field. + * @param {number} field The field number. + * @param {?Array|undefined} value The array of ints + * to write. + */ + writeRepeatedInt64(field, value) { + if (value == null) return; + for (let i = 0; i < value.length; i++) { + this.writeSignedVarint64_(field, value[i]); + } } -}; + /** + * Writes an array of 64-bit values to the buffer as a fixed64. + * @param {number} field The field number. + * @param {?Array|undefined} 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 + */ + writeRepeatedSplitFixed64(field, value, lo, hi) { + if (value == null) return; + for (let i = 0; i < value.length; i++) { + this.writeSplitFixed64(field, lo(value[i]), hi(value[i])); + } + } -/** - * Writes a repeated 64-bit hash string field (8 characters @ 8 bits of data - * each) to the buffer. - * @param {number} field The field number. - * @param {?Array} value The array of hashes to write. - * @export - */ -jspb.BinaryWriter.prototype.writeRepeatedVarintHash64 = function(field, value) { - if (value == null) return; - for (var i = 0; i < value.length; i++) { - this.writeVarintHash64(field, value[i]); + /** + * Writes an array of 64-bit values to the buffer as a varint. + * @param {number} field The field number. + * @param {?Array|undefined} 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 + */ + writeRepeatedSplitVarint64(field, value, lo, hi) { + if (value == null) return; + for (let i = 0; i < value.length; i++) { + this.writeSplitVarint64(field, lo(value[i]), hi(value[i])); + } } -}; + /** + * Writes an array of 64-bit values to the buffer as a zigzag varint. + * @param {number} field The field number. + * @param {?Array|undefined} 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 + */ + writeRepeatedSplitZigzagVarint64(field, value, lo, hi) { + if (value == null) return; + for (let i = 0; i < value.length; i++) { + this.writeSplitZigzagVarint64(field, lo(value[i]), hi(value[i])); + } + } -/** - * Writes an array of numbers to the buffer as a packed 32-bit int field. - * @param {number} field The field number. - * @param {?Array} value The array of ints to write. - * @export - */ -jspb.BinaryWriter.prototype.writePackedInt32 = 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]); + /** + * Writes an array of numbers formatted as strings to the buffer as a repeated + * 64-bit int field. + * @param {number} field The field number. + * @param {?Array|undefined} value The array of ints to write. + * @deprecated use writeRepeatedInt64(). + */ + writeRepeatedInt64String(field, value) { + this.writeRepeatedInt64(field, value); } - this.endDelimited_(bookmark); -}; + /** + * Writes an array numbers to the buffer as a repeated unsigned 32-bit int + * field. + * @param {number} field The field number. + * @param {?Array|undefined} value The array of ints to write. + */ + writeRepeatedUint32(field, value) { + if (value == null) return; + for (let i = 0; i < value.length; i++) { + this.writeUnsignedVarint32_(field, value[i]); + } + } -/** - * Writes an array of numbers represented as strings to the buffer as a packed - * 32-bit int field. - * @param {number} field - * @param {?Array} value - * @export - */ -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.encoder_.writeSignedVarint32(parseInt(value[i], 10)); + /** + * Writes an array numbers or decimal strings to the buffer as a repeated + * unsigned 64-bit int field. + * @param {number} field The field number. + * @param {?Array|undefined} value The array of ints + * to write. + */ + writeRepeatedUint64(field, value) { + if (value == null) return; + for (let i = 0; i < value.length; i++) { + this.writeUnsignedVarint64_(field, value[i]); + } } - this.endDelimited_(bookmark); -}; + /** + * Writes an array of numbers formatted as strings to the buffer as a repeated + * unsigned 64-bit int field. + * @param {number} field The field number. + * @param {?Array|undefined} value The array of ints to write. + * @deprecated Use writeRepeatedUint64(). + */ + writeRepeatedUint64String(field, value) { + this.writeRepeatedUint64(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} value The array of ints to write. - * @export - */ -jspb.BinaryWriter.prototype.writePackedInt64 = 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]); + /** + * Writes an array numbers to the buffer as a repeated signed 32-bit int + * field. + * @param {number} field The field number. + * @param {?Array|undefined} value The array of ints to write. + */ + writeRepeatedSint32(field, value) { + if (value == null) return; + for (let i = 0; i < value.length; i++) { + this.writeZigzagVarint32_(field, value[i]); + } } - this.endDelimited_(bookmark); -}; + /** + * Writes an array numbers or decimal strings to the buffer as a repeated + * signed 64-bit int field. + * @param {number} field The field number. + * @param {?Array|undefined} value The array of ints + * to write. + */ + writeRepeatedSint64(field, value) { + if (value == null) return; + for (let i = 0; i < value.length; i++) { + this.writeZigzagVarint64_(field, value[i]); + } + } -/** - * Writes an array of 64-bit values to the buffer as a fixed64. - * @param {number} field The field number. - * @param {?Array} 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 - * @export - */ -jspb.BinaryWriter.prototype.writePackedSplitFixed64 = function( - field, value, lo, hi) { - if (value == null) return; - var bookmark = this.beginDelimited_(field); - for (var i = 0; i < value.length; i++) { - this.encoder_.writeSplitFixed64(lo(value[i]), hi(value[i])); + /** + * Writes an array numbers to the buffer as a repeated signed 64-bit int + * field. + * @param {number} field The field number. + * @param {?Array|undefined} value The array of ints to write. + * @deprecated Use writeRepeatedSint64(). + */ + writeRepeatedSint64String(field, value) { + this.writeRepeatedSint64(field, value); } - this.endDelimited_(bookmark); -}; + /** + * 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. + * @param {?Array|undefined} value The array of ints to write. + */ + writeRepeatedFixed32(field, value) { + if (value == null) return; + for (let i = 0; i < value.length; i++) { + this.writeFixed32(field, value[i]); + } + } -/** - * Writes an array of 64-bit values to the buffer as a varint. - * @param {number} field The field number. - * @param {?Array} 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 - * @export - */ -jspb.BinaryWriter.prototype.writePackedSplitVarint64 = function( - field, value, lo, hi) { - if (value == null) return; - var bookmark = this.beginDelimited_(field); - for (var i = 0; i < value.length; i++) { - this.encoder_.writeSplitVarint64(lo(value[i]), hi(value[i])); + /** + * Writes an array of numbers or decimal strings to the buffer as a repeated + * fixed64 field. This works for both signed and unsigned fixed64s. + * @param {number} field The field number. + * @param {?Array|undefined} value The array of ints + * to write. + */ + writeRepeatedFixed64(field, value) { + if (value == null) return; + for (let i = 0; i < value.length; i++) { + this.writeFixed64(field, value[i]); + } } - this.endDelimited_(bookmark); -}; + /** + * Writes an array of numbers to the buffer as a repeated fixed64 field. This + * works for both signed and unsigned fixed64s. + * @param {number} field The field number. + * @param {?Array|undefined} value The array of decimal strings to + * write. + * @deprecated Use writeRepeatedFixed64(). + */ + writeRepeatedFixed64String(field, value) { + this.writeRepeatedFixed64(field, value); + } -/** - * Writes an array of 64-bit values to the buffer as a zigzag varint. - * @param {number} field The field number. - * @param {?Array} 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 - * @export - */ -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); - }); + /** + * Writes an array of numbers to the buffer as a repeated sfixed32 field. + * @param {number} field The field number. + * @param {?Array|undefined} value The array of ints to write. + */ + writeRepeatedSfixed32(field, value) { + if (value == null) return; + for (let i = 0; i < value.length; i++) { + this.writeSfixed32(field, value[i]); + } } - this.endDelimited_(bookmark); -}; + /** + * Writes an array of numbers or decimal strings to the buffer as a repeated + * sfixed64 field. + * @param {number} field The field number. + * @param {?Array|undefined} value The array of ints to write. + */ + writeRepeatedSfixed64(field, value) { + if (value == null) return; + for (let i = 0; i < value.length; i++) { + this.writeSfixed64(field, value[i]); + } + } -/** - * Writes an array of numbers represented as strings to the buffer as a packed - * 64-bit int field. - * @param {number} field - * @param {?Array} value - * @export - */ -jspb.BinaryWriter.prototype.writePackedInt64String = function(field, value) { - if (value == null || !value.length) return; - var bookmark = this.beginDelimited_(field); - for (var i = 0; i < value.length; i++) { - var num = jspb.arith.Int64.fromString(value[i]); - this.encoder_.writeSplitVarint64(num.lo, num.hi); + /** + * Writes an array of decimal strings to the buffer as a repeated sfixed64 + * field. + * @param {number} field The field number. + * @param {?Array|undefined} value The array of decimal strings to + * write. + * @deprecated Use writeRepeatedSfixed64(). + */ + writeRepeatedSfixed64String(field, value) { + this.writeRepeatedSfixed64(field, value); } - this.endDelimited_(bookmark); -}; + /** + * Writes an array of numbers to the buffer as a repeated float field. + * @param {number} field The field number. + * @param {?Array|undefined} value The array of floats to + * write, accepts 'Infinity'/'-Infinity'/'NaN' for JSPB wire format + * compatibility. + */ + writeRepeatedFloat(field, value) { + if (value == null) return; + for (let i = 0; i < value.length; i++) { + this.writeFloat(field, value[i]); + } + } -/** - * Writes an array numbers to the buffer as a packed unsigned 32-bit int field. - * @param {number} field The field number. - * @param {?Array} value The array of ints to write. - * @export - */ -jspb.BinaryWriter.prototype.writePackedUint32 = 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]); + /** + * Writes an array of numbers to the buffer as a repeated double field. + * @param {number} field The field number. + * @param {?Array|undefined} value The array of doubles to + * write, accepts 'Infinity'/'-Infinity'/'NaN' for JSPB wire format + * compatibility. + */ + writeRepeatedDouble(field, value) { + if (value == null) return; + for (let i = 0; i < value.length; i++) { + this.writeDouble(field, value[i]); + } } - this.endDelimited_(bookmark); -}; + /** + * Writes an array of booleans to the buffer as a repeated bool field. + * @param {number} field The field number. + * @param {?Array|undefined} value The array of booleans to + * write. + */ + writeRepeatedBool(field, value) { + if (value == null) return; + for (let i = 0; i < value.length; i++) { + this.writeBool(field, value[i]); + } + } -/** - * Writes an array of numbers represented as strings to the buffer as a packed - * unsigned 32-bit int field. - * @param {number} field - * @param {?Array} value - * @export - */ -jspb.BinaryWriter.prototype.writePackedUint32String = 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(parseInt(value[i], 10)); + /** + * Writes an array of enums to the buffer as a repeated enum field. + * @param {number} field The field number. + * @param {?Array|undefined} value The array of ints to write. + */ + writeRepeatedEnum(field, value) { + if (value == null) return; + for (let i = 0; i < value.length; i++) { + this.writeEnum(field, value[i]); + } } - this.endDelimited_(bookmark); -}; + /** + * Writes an array of strings to the buffer as a repeated string field. + * @param {number} field The field number. + * @param {?Array|undefined} value The array of strings to write. + */ + writeRepeatedString(field, value) { + if (value == null) return; + for (let i = 0; i < value.length; i++) { + this.writeString(field, value[i]); + } + } -/** - * Writes an array numbers to the buffer as a packed unsigned 64-bit int field. - * @param {number} field The field number. - * @param {?Array} value The array of ints to write. - * @export - */ -jspb.BinaryWriter.prototype.writePackedUint64 = 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]); + /** + * Writes an array of arbitrary byte fields to the buffer. + * @param {number} field The field number. + * @param {?Array|undefined} value The arrays of + * arrays of bytes to write. + */ + writeRepeatedBytes(field, value) { + if (value == null) return; + for (let i = 0; i < value.length; i++) { + this.writeBytes(field, value[i]); + } } - this.endDelimited_(bookmark); -}; + /** + * Writes an array of messages to the buffer. + * @template MessageType + * @param {number} field The field number. + * @param {?Array|undefined} value The array of messages to + * write. + * @param {function(MessageType, !BinaryWriter)} writerCallback + * Will be invoked with the value to write and the writer to write it + * with. + */ + writeRepeatedMessage(field, value, writerCallback) { + if (value == null) return; + for (let i = 0; i < value.length; i++) { + const bookmark = this.beginDelimited_(field); + writerCallback(value[i], this); + this.endDelimited_(bookmark); + } + } -/** - * Writes an array of numbers represented as strings to the buffer as a packed - * unsigned 64-bit int field. - * @param {number} field - * @param {?Array} value - * @export - */ -jspb.BinaryWriter.prototype.writePackedUint64String = function(field, value) { - if (value == null || !value.length) return; - var bookmark = this.beginDelimited_(field); - for (var i = 0; i < value.length; i++) { - var num = jspb.arith.UInt64.fromString(value[i]); - this.encoder_.writeSplitVarint64(num.lo, num.hi); + /** + * Writes an array of group messages to the buffer. + * @template MessageType + * @param {number} field The field number. + * @param {?Array|undefined} value The array of messages to + * write. + * @param {function(MessageType, !BinaryWriter)} writerCallback + * Will be invoked with the value to write and the writer to write it + * with. + */ + writeRepeatedGroup(field, value, writerCallback) { + if (value == null) return; + for (let i = 0; i < value.length; i++) { + this.writeFieldHeader_(field, WireType.START_GROUP); + writerCallback(value[i], this); + this.writeFieldHeader_(field, WireType.END_GROUP); + } } - this.endDelimited_(bookmark); -}; + /** + * Writes an array of numbers to the buffer as a packed 32-bit int field. + * @param {number} field The field number. + * @param {?Array|undefined} value The array of ints to write. + */ + writePackedInt32(field, value) { + if (value == null || !value.length) return; + const bookmark = this.beginDelimited_(field); + for (let i = 0; i < value.length; i++) { + assertSignedInteger(field, value[i]); + this.encoder_.writeSignedVarint32(value[i]); + } + this.endDelimited_(bookmark); + } -/** - * Writes an array numbers to the buffer as a packed signed 32-bit int field. - * @param {number} field The field number. - * @param {?Array} value The array of ints to write. - * @export - */ -jspb.BinaryWriter.prototype.writePackedSint32 = 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]); + /** + * Writes an array of numbers represented as strings to the buffer as a packed + * 32-bit int field. + * @param {number} field + * @param {?Array|undefined} value + */ + writePackedInt32String(field, value) { + if (value == null || !value.length) return; + const bookmark = this.beginDelimited_(field); + for (let i = 0; i < value.length; i++) { + const intValue = parseInt(value[i], 10); + assertSignedInteger(field, intValue); + this.encoder_.writeSignedVarint32(intValue); + } + this.endDelimited_(bookmark); } - this.endDelimited_(bookmark); -}; + /** + * Writes an array of numbers or decimal strings to the buffer as a packed + * 64-bit int field. + * @param {number} field The field number. + * @param {?Array|undefined} value The array of ints + * to write. + */ + writePackedInt64(field, value) { + if (value == null || !value.length) return; + const bookmark = this.beginDelimited_(field); + for (let i = 0; i < value.length; i++) { + const v = value[i]; + switch (typeof v) { + case 'number': + this.encoder_.writeSignedVarint64(v); + break; + + case 'bigint': { + const num = Int64.fromBigInt(/** @type {bigint} */(v)); + this.encoder_.writeSplitVarint64(num.lo, num.hi); + break; + } + + default: { + const num = Int64.fromString(/** @type {string} */(v)); + this.encoder_.writeSplitVarint64(num.lo, num.hi); + break; + } + } + } + this.endDelimited_(bookmark); + } -/** - * Writes an array of numbers to the buffer as a packed signed 64-bit int field. - * @param {number} field The field number. - * @param {?Array} value The array of ints to write. - * @export - */ -jspb.BinaryWriter.prototype.writePackedSint64 = 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]); + /** + * Writes an array of 64-bit values to the buffer as a fixed64. + * @param {number} field The field number. + * @param {?Array|undefined} 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 + */ + writePackedSplitFixed64(field, value, lo, hi) { + if (value == null) return; + const bookmark = this.beginDelimited_(field); + for (let i = 0; i < value.length; i++) { + this.encoder_.writeSplitFixed64(lo(value[i]), hi(value[i])); + } + this.endDelimited_(bookmark); } - this.endDelimited_(bookmark); -}; + /** + * Writes an array of 64-bit values to the buffer as a varint. + * @param {number} field The field number. + * @param {?Array|undefined} 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 + */ + writePackedSplitVarint64(field, value, lo, hi) { + if (value == null) return; + const bookmark = this.beginDelimited_(field); + for (let i = 0; i < value.length; i++) { + this.encoder_.writeSplitVarint64(lo(value[i]), hi(value[i])); + } + this.endDelimited_(bookmark); + } -/** - * Writes an array of decimal strings to the buffer as a packed signed 64-bit - * int field. - * @param {number} field The field number. - * @param {?Array} value The array of decimal strings to write. - * @export - */ -jspb.BinaryWriter.prototype.writePackedSint64String = 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( - jspb.utils.decimalStringToHash64(value[i])); + /** + * Writes an array of 64-bit values to the buffer as a zigzag varint. + * @param {number} field The field number. + * @param {?Array|undefined} 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 + */ + writePackedSplitZigzagVarint64(field, value, lo, hi) { + if (value == null) return; + const bookmark = this.beginDelimited_(field); + const encoder = this.encoder_; + for (let i = 0; i < value.length; i++) { + encoder.writeSplitZigzagVarint64(lo(value[i]), hi(value[i])); + } + this.endDelimited_(bookmark); } - this.endDelimited_(bookmark); -}; + /** + * Writes an array of numbers represented as strings to the buffer as a packed + * 64-bit int field. + * @param {number} field + * @param {?Array|undefined} value + * @deprecated Use writePackedInt64(). + */ + writePackedInt64String(field, value) { + this.writePackedInt64(field, value); + } -/** - * 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} value The array of decimal strings to write. - * @export - */ -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]); + /** + * Writes an array numbers to the buffer as a packed unsigned 32-bit int + * field. + * @param {number} field The field number. + * @param {?Array|undefined} value The array of ints to write. + */ + writePackedUint32(field, value) { + if (value == null || !value.length) return; + const bookmark = this.beginDelimited_(field); + for (let i = 0; i < value.length; i++) { + this.encoder_.writeUnsignedVarint32(value[i]); + } + this.endDelimited_(bookmark); } - this.endDelimited_(bookmark); -}; + /** + * Writes an array numbers or decimal strings to the buffer as a packed + * unsigned 64-bit int field. + * @param {number} field The field number. + * @param {?Array|undefined} value The array of ints + * to write. + */ + writePackedUint64(field, value) { + if (value == null || !value.length) return; + const bookmark = this.beginDelimited_(field); + for (let i = 0; i < value.length; i++) { + const v = value[i]; + switch (typeof v) { + case 'number': + this.encoder_.writeUnsignedVarint64(v); + break; + + case 'bigint': + const n = Number(v); + if (Number.isSafeInteger(n)) { + this.encoder_.writeUnsignedVarint64(n); + } else { + const num = UInt64.fromBigInt(/** @type {bigint} */(v)); + this.encoder_.writeSplitVarint64(num.lo, num.hi); + } + break; + + default: + const num = UInt64.fromString(/** @type {string} */(v)); + this.encoder_.writeSplitVarint64(num.lo, num.hi); + break; + } + } + this.endDelimited_(bookmark); + } -/** - * Writes an array of numbers to the buffer as a packed fixed32 field. - * @param {number} field The field number. - * @param {?Array} value The array of ints to write. - * @export - */ -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 represented as strings to the buffer as a packed + * unsigned 64-bit int field. + * @param {number} field + * @param {?Array|undefined} value + * @deprecated Use writePackedUint64() + */ + writePackedUint64String(field, value) { + this.writePackedUint64(field, value); } -}; + /** + * Writes an array numbers to the buffer as a packed signed 32-bit int field. + * @param {number} field The field number. + * @param {?Array|undefined} value The array of ints to write. + */ + writePackedSint32(field, value) { + if (value == null || !value.length) return; + const bookmark = this.beginDelimited_(field); + for (let 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 fixed64 field. - * @param {number} field The field number. - * @param {?Array} value The array of ints to write. - * @export - */ -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 or decimal strings to the buffer as a packed + * signed 64-bit int field. + * @param {number} field The field number. + * @param {?Array|undefined} value The array of ints + * to write. + */ + writePackedSint64(field, value) { + if (value == null || !value.length) return; + const bookmark = this.beginDelimited_(field); + for (let i = 0; i < value.length; i++) { + const v = value[i]; + switch (typeof v) { + case 'number': + this.encoder_.writeZigzagVarint64(v); + break; + + case 'bigint': + this.encoder_.writeZigzagVarint64BigInt(/** @type {bigint} */(v)); + break; + + default: + this.encoder_.writeZigzagVarint64String(/** @type {string} */(v)); + break; + } + } + this.endDelimited_(bookmark); } -}; + /** + * Writes an array of decimal strings to the buffer as a packed signed 64-bit + * int field. + * @param {number} field The field number. + * @param {?Array|undefined} value The array of decimal strings to + * write. + * @deprecated Use writePackedSint64(). + */ + writePackedSint64String(field, value) { + this.writePackedSint64(field, value); + } -/** - * Writes an array of numbers represented as strings to the buffer as a packed - * fixed64 field. - * @param {number} field The field number. - * @param {?Array} value The array of strings to write. - * @export - */ -jspb.BinaryWriter.prototype.writePackedFixed64String = 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++) { - var num = jspb.arith.UInt64.fromString(value[i]); - this.encoder_.writeSplitFixed64(num.lo, num.hi); + /** + * Writes an array of numbers to the buffer as a packed fixed32 field. + * @param {number} field The field number. + * @param {?Array|undefined} value The array of ints to write. + */ + writePackedFixed32(field, value) { + if (value == null || !value.length) return; + this.writeFieldHeader_(field, WireType.DELIMITED); + this.encoder_.writeUnsignedVarint32(value.length * 4); + for (let i = 0; i < value.length; i++) { + this.encoder_.writeUint32(value[i]); + } } -}; + /** + * Writes an array of numbers or decimal strings to the buffer as a packed + * fixed64 field. + * @param {number} field The field number. + * @param {?Array|undefined} value The array of ints + * to write. + */ + writePackedFixed64(field, value) { + if (value == null || !value.length) return; + this.writeFieldHeader_(field, WireType.DELIMITED); + this.encoder_.writeUnsignedVarint32(value.length * 8); + for (let i = 0; i < value.length; i++) { + const v = value[i]; + switch (typeof v) { + case 'number': + this.encoder_.writeUint64(/** @type {number} */(v)); + break; + + case 'bigint': + const fromBigint = UInt64.fromBigInt(/** @type {bigint} */(v)); + this.encoder_.writeSplitFixed64(fromBigint.lo, fromBigint.hi); + break; + + + default: + const fromString = UInt64.fromString(/** @type {string} */(v)); + this.encoder_.writeSplitFixed64(fromString.lo, fromString.hi); + break; + } + } + } -/** - * Writes an array of numbers to the buffer as a packed sfixed32 field. - * @param {number} field The field number. - * @param {?Array} value The array of ints to write. - * @export - */ -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 represented as strings to the buffer as a packed + * fixed64 field. + * @param {number} field The field number. + * @param {?Array|undefined} value The array of strings to write. + * @deprecated Use writePackedFixed64(). + */ + writePackedFixed64String(field, value) { + this.writePackedFixed64(field, value); } -}; + /** + * Writes an array of numbers to the buffer as a packed sfixed32 field. + * @param {number} field The field number. + * @param {?Array|undefined} value The array of ints to write. + */ + writePackedSfixed32(field, value) { + if (value == null || !value.length) return; + this.writeFieldHeader_(field, WireType.DELIMITED); + this.encoder_.writeUnsignedVarint32(value.length * 4); + for (let i = 0; i < value.length; i++) { + this.encoder_.writeInt32(value[i]); + } + } -/** - * Writes an array of numbers to the buffer as a packed sfixed64 field. - * @param {number} field The field number. - * @param {?Array} value The array of ints to write. - * @export - */ -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 or decimal strings to the buffer as a packed + * sfixed64 field. + * @param {number} field The field number. + * @param {?Array|undefined} value The array of ints + * to write. + */ + writePackedSfixed64(field, value) { + if (value == null || !value.length) return; + this.writeFieldHeader_(field, WireType.DELIMITED); + this.encoder_.writeUnsignedVarint32(value.length * 8); + for (let i = 0; i < value.length; i++) { + const v = value[i]; + switch (typeof v) { + case 'number': + this.encoder_.writeInt64(v); + break; + + case 'bigint': { + const num = Int64.fromBigInt(/** @type {bigint} */(v)); + this.encoder_.writeSplitFixed64(num.lo, num.hi); + break; + } + + default: { + const num = Int64.fromString(/** @type {string} */(v)); + this.encoder_.writeSplitFixed64(num.lo, num.hi); + break; + } + } + } } -}; + /** + * Writes an array of numbers or decimal strings to the buffer as a packed + * sfixed64 field. + * @param {number} field The field number. + * @param {?Array|undefined} value The array of ints to write. + * @deprecated Use writePackedSfixed64() + */ + writePackedSfixed64String(field, value) { + this.writePackedSfixed64(field, value); + } -/** - * Writes an array of numbers to the buffer as a packed sfixed64 field. - * @param {number} field The field number. - * @param {?Array} value The array of decimal strings to write. - * @export - */ -jspb.BinaryWriter.prototype.writePackedSfixed64String = 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_.writeInt64String(value[i]); + /** + * Writes an array of numbers to the buffer as a packed float field. + * @param {number} field The field number. + * @param {?Array|undefined} value The array of floats to + * write, accepts 'Infinity'/'-Infinity'/'NaN' for JSPB wire format + * compatibility. + */ + writePackedFloat(field, value) { + if (value == null || !value.length) return; + this.writeFieldHeader_(field, WireType.DELIMITED); + this.encoder_.writeUnsignedVarint32(value.length * 4); + for (let i = 0; i < value.length; i++) { + this.encoder_.writeFloat(value[i]); + } } -}; + /** + * Writes an array of numbers to the buffer as a packed double field. + * @param {number} field The field number. + * @param {?Array|undefined} value The array of doulbe to + * write, accepts 'Infinity'/'-Infinity'/'NaN' for JSPB wire format + * compatibility. + */ + writePackedDouble(field, value) { + if (value == null || !value.length) return; + this.writeFieldHeader_(field, WireType.DELIMITED); + this.encoder_.writeUnsignedVarint32(value.length * 8); + for (let i = 0; i < value.length; i++) { + this.encoder_.writeDouble(value[i]); + } + } -/** - * Writes an array of numbers to the buffer as a packed float field. - * @param {number} field The field number. - * @param {?Array} value The array of ints to write. - * @export - */ -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 booleans to the buffer as a packed bool field. + * @param {number} field The field number. + * @param {?Array|undefined} value The array of booleans to + * write. + */ + writePackedBool(field, value) { + if (value == null || !value.length) return; + this.writeFieldHeader_(field, WireType.DELIMITED); + this.encoder_.writeUnsignedVarint32(value.length); + for (let i = 0; i < value.length; i++) { + this.encoder_.writeBool(value[i]); + } } -}; + /** + * Writes an array of enums to the buffer as a packed enum field. + * @param {number} field The field number. + * @param {?Array|undefined} value The array of ints to write. + */ + writePackedEnum(field, value) { + if (value == null || !value.length) return; + const bookmark = this.beginDelimited_(field); + for (let i = 0; i < value.length; i++) { + this.encoder_.writeEnum(value[i]); + } + this.endDelimited_(bookmark); + } +} /** - * Writes an array of numbers to the buffer as a packed double field. - * @param {number} field The field number. - * @param {?Array} value The array of ints to write. - * @export + * Asserts that the value is signed 32-bit integer. + * @param {number} field + * @param {?} value */ -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]); - } -}; - +function assertSignedInteger(field, value) { + assertThat(field, value, value === Math.floor(value)); + assertThat(field, value, value >= -TWO_TO_31 && value < TWO_TO_31); +} /** - * Writes an array of booleans to the buffer as a packed bool field. - * @param {number} field The field number. - * @param {?Array} value The array of ints to write. - * @export - */ -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]); + * Asserts that the value is an unsigned 64-bit integer. + * NOTE: does not yet validate the range of decimal strings, such a change + * would need to be tested before becoming more strict. + * @param {number} field + * @param {*} value + */ +function assertSignedInt64(field, value) { + const typeofValue = typeof value; + switch (typeofValue) { + case 'string': { + const valueStr = /** @type {string} */ (value); + assertThat(field, valueStr, Int64.fromString(valueStr)); + break; + } + + case 'number': { + const valueNum = /** @type {number} */ (value); + assertThat( + field, valueNum, valueNum >= -TWO_TO_63 && valueNum < TWO_TO_63); + break; + } + + default: { + const valueBig = /** @type {bigint} */ (value); + assertThat( + field, valueBig, + valueBig >= BigInt(-TWO_TO_63) && valueBig < BigInt(TWO_TO_63)); + break; + } } -}; - +} /** - * Writes an array of enums to the buffer as a packed enum field. - * @param {number} field The field number. - * @param {?Array} value The array of ints to write. - * @export - */ -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]); + * Asserts that the value is signed 64-bit integer. + * NOTE: does not yet validate the range of decimal strings, such a change + * would need to be tested before becoming more strict. + * @param {number} field + * @param {*} value + */ +function assertUnsignedInt64(field, value) { + const typeofValue = typeof value; + switch (typeofValue) { + case 'string': { + const valueStr = /** @type {string} */ (value); + assertThat(field, valueStr, UInt64.fromString(valueStr)); + break; + } + + case 'number': { + const valueNum = /** @type {number} */ (value); + assertThat(field, valueNum, valueNum >= 0 && valueNum < TWO_TO_64); + break; + } + + default: { + const valueBig = /** @type {bigint} */ (value); + assertThat( + field, valueBig, + valueBig >= BigInt(0) && valueBig < BigInt(TWO_TO_64)); + break; + } } - this.endDelimited_(bookmark); -}; - +} /** - * Writes a 64-bit hash string field (8 characters @ 8 bits of data each) to - * the buffer. - * @param {number} field The field number. - * @param {?Array} value The array of hashes to write. - * @export - */ -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]); + * Asserts the condition. + * @param {number} field + * @param {?} value + * @param {?} condition + */ +function assertThat(field, value, condition) { + // Manual assertion here instead of asserts.assert(...) call for perf + // reasons, to avoid unnecessary string concatenations. + if (!condition) { + fail(`for [${value}] at [${field}]`); } -}; +} -/** - * Writes a 64-bit hash string field (8 characters @ 8 bits of data each) to - * the buffer. - * @param {number} field The field number. - * @param {?Array} value The array of hashes to write. - * @export - */ -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); -}; +exports = { BinaryWriter }; diff --git a/binary/writer_alias.js b/binary/writer_alias.js new file mode 100755 index 0000000..4f41188 --- /dev/null +++ b/binary/writer_alias.js @@ -0,0 +1,9 @@ +/** + * @fileoverview Legacy alias for the old namespace used by writer.js + */ +goog.module('jspb.BinaryWriter'); +goog.module.declareLegacyNamespace(); + +const {BinaryWriter} = goog.require('jspb.binary.writer'); + +exports = BinaryWriter; diff --git a/binary/writer_test.js b/binary/writer_test.js old mode 100644 new mode 100755 index a540934..2b6fc0c --- a/binary/writer_test.js +++ b/binary/writer_test.js @@ -38,16 +38,28 @@ * @author aappleby@google.com (Austin Appleby) */ -goog.require('goog.crypt'); -goog.require('goog.crypt.base64'); - goog.require('jspb.BinaryConstants'); goog.require('jspb.BinaryReader'); +goog.require('jspb.binary.test_utils'); goog.require('jspb.BinaryWriter'); +goog.require('jspb.bytestring'); goog.require('jspb.utils'); +goog.require('goog.crypt'); +goog.require('goog.crypt.base64'); -goog.requireType('jspb.BinaryMessage'); - +const BinaryMessage = goog.module.get('jspb.BinaryConstants').BinaryMessage; +const BinaryReader = goog.module.get('jspb.BinaryReader'); +const BinaryWriter = goog.module.get('jspb.BinaryWriter'); +const ByteString = goog.module.get('jspb.bytestring').ByteString; +const FLOAT32_MAX = goog.module.get('jspb.BinaryConstants').FLOAT32_MAX; +const FLOAT32_MIN = goog.module.get('jspb.BinaryConstants').FLOAT32_MIN; +const base64 = goog.module.get('goog.crypt.base64'); +const byteArrayToHex = goog.module.get('goog.crypt').byteArrayToHex; +const byteSourceToUint8Array = goog.module.get('jspb.utils').byteSourceToUint8Array; +const getSplit64High = goog.module.get('jspb.utils').getSplit64High; +const getSplit64Low = goog.module.get('jspb.utils').getSplit64Low; +const splitDecimalString = goog.module.get('jspb.utils').splitDecimalString; +const toHexFields = goog.module.get('jspb.binary.test_utils').toHexFields; describe('binaryWriterTest', () => { /** @@ -55,91 +67,91 @@ describe('binaryWriterTest', () => { */ it('testWriteErrors', () => { // Submessages with invalid field indices should assert. - let writer = new jspb.BinaryWriter(); - const dummyMessage = /** @type {!jspb.BinaryMessage} */ ({}); + let writer = new BinaryWriter(); + const dummyMessage = /** @type {!BinaryMessage} */ ({}); expect(() => { - writer.writeMessage(-1, dummyMessage, () => {}); - }).toThrow(); + writer.writeMessage(-1, dummyMessage, goog.nullFunction); + }).toThrowError(); // Writing invalid field indices should assert. - writer = new jspb.BinaryWriter(); + writer = new BinaryWriter(); expect(() => { writer.writeUint64(-1, 1); - }).toThrow(); + }).toThrowError(); // Writing out-of-range field values should assert. - writer = new jspb.BinaryWriter(); + writer = new BinaryWriter(); expect(() => { writer.writeInt32(1, -Infinity); - }).toThrow(); + }).toThrowError(); expect(() => { writer.writeInt32(1, Infinity); - }).toThrow(); + }).toThrowError(); expect(() => { writer.writeInt64(1, -Infinity); - }).toThrow(); + }).toThrowError(); expect(() => { writer.writeInt64(1, Infinity); - }).toThrow(); + }).toThrowError(); expect(() => { writer.writeUint32(1, -1); - }).toThrow(); + }).toThrowError(); expect(() => { writer.writeUint32(1, Infinity); - }).toThrow(); + }).toThrowError(); expect(() => { writer.writeUint64(1, -1); - }).toThrow(); + }).toThrowError(); expect(() => { writer.writeUint64(1, Infinity); - }).toThrow(); + }).toThrowError(); expect(() => { writer.writeSint32(1, -Infinity); - }).toThrow(); + }).toThrowError(); expect(() => { writer.writeSint32(1, Infinity); - }).toThrow(); + }).toThrowError(); expect(() => { writer.writeSint64(1, -Infinity); - }).toThrow(); + }).toThrowError(); expect(() => { writer.writeSint64(1, Infinity); - }).toThrow(); + }).toThrowError(); expect(() => { writer.writeFixed32(1, -1); - }).toThrow(); + }).toThrowError(); expect(() => { writer.writeFixed32(1, Infinity); - }).toThrow(); + }).toThrowError(); expect(() => { writer.writeFixed64(1, -1); - }).toThrow(); + }).toThrowError(); expect(() => { writer.writeFixed64(1, Infinity); - }).toThrow(); + }).toThrowError(); expect(() => { writer.writeSfixed32(1, -Infinity); - }).toThrow(); + }).toThrowError(); expect(() => { writer.writeSfixed32(1, Infinity); - }).toThrow(); + }).toThrowError(); expect(() => { writer.writeSfixed64(1, -Infinity); - }).toThrow(); + }).toThrowError(); expect(() => { writer.writeSfixed64(1, Infinity); - }).toThrow(); + }).toThrowError(); }); @@ -149,14 +161,14 @@ describe('binaryWriterTest', () => { it('testGetResultBuffer', () => { const expected = '0864120b48656c6c6f20776f726c641a0301020320c801'; - const writer = new jspb.BinaryWriter(); + const writer = new BinaryWriter(); writer.writeUint32(1, 100); writer.writeString(2, 'Hello world'); writer.writeBytes(3, new Uint8Array([1, 2, 3])); writer.writeUint32(4, 200); const buffer = writer.getResultBuffer(); - expect(goog.crypt.byteArrayToHex(buffer)).toEqual(expected); + expect(byteArrayToHex(buffer)).toEqual(expected); }); @@ -164,17 +176,17 @@ describe('binaryWriterTest', () => { * Tests websafe encodings for base64 strings. */ it('testWebSafeOption', () => { - const writer = new jspb.BinaryWriter(); + const writer = new BinaryWriter(); writer.writeBytes(1, new Uint8Array([127])); - expect('CgF/').toEqual(writer.getResultBase64String()); - expect('CgF/').toEqual( - writer.getResultBase64String(/* goog.crypt.base64.Alphabet.DEFAULT = */ 0)); - expect('CgF_').toEqual(writer.getResultBase64String( - /* goog.crypt.base64.Alphabet.WEBSAFE_NO_PADDING = */ 4)); + expect(writer.getResultBase64String()).toEqual('CgF/'); + expect(writer.getResultBase64String(base64.Alphabet.DEFAULT)) + .toEqual('CgF/'); + expect(writer.getResultBase64String(base64.Alphabet.WEBSAFE_NO_PADDING)) + .toEqual('CgF_'); }); it('writes split 64 fields', () => { - const writer = new jspb.BinaryWriter(); + const writer = new BinaryWriter(); writer.writeSplitVarint64(1, 0x1, 0x2); writer.writeSplitVarint64(1, 0xFFFFFFFF, 0xFFFFFFFF); writer.writeSplitFixed64(2, 0x1, 0x2); @@ -190,10 +202,7 @@ describe('binaryWriterTest', () => { writer.writePackedSplitVarint64(5, [0, 1, 2], lo, hi); writer.writePackedSplitFixed64(6, [0, 1, 2], lo, hi); - function bitsAsArray(lowBits, highBits) { - return [lowBits >>> 0, highBits >>> 0]; - } - const reader = jspb.BinaryReader.alloc(writer.getResultBuffer()); + const reader = BinaryReader.alloc(writer.getResultBuffer()); reader.nextField(); expect(reader.getFieldNumber()).toEqual(1); expect(reader.readSplitVarint64(bitsAsArray)).toEqual([0x1, 0x2]); @@ -201,7 +210,8 @@ describe('binaryWriterTest', () => { reader.nextField(); expect(reader.getFieldNumber()).toEqual(1); expect(reader.readSplitVarint64(bitsAsArray)).toEqual([ - 0xFFFFFFFF, 0xFFFFFFFF + 0xFFFFFFFF, + 0xFFFFFFFF, ]); reader.nextField(); @@ -211,7 +221,8 @@ describe('binaryWriterTest', () => { reader.nextField(); expect(reader.getFieldNumber()).toEqual(2); expect(reader.readSplitFixed64(bitsAsArray)).toEqual([ - 0xFFFFFFF0, 0xFFFFFFFF + 0xFFFFFFF0, + 0xFFFFFFFF, ]); for (let i = 0; i < 3; i++) { @@ -228,7 +239,8 @@ describe('binaryWriterTest', () => { reader.nextField(); expect(reader.getFieldNumber()).toEqual(5); - expect(reader.readPackedInt64String()).toEqual([ + const a = []; + expect((reader.readPackableInt64StringInto(a), a)).toEqual([ String(2 * 2 ** 32 + 1), String(3 * 2 ** 32 + 2), String(4 * 2 ** 32 + 3), @@ -236,15 +248,88 @@ describe('binaryWriterTest', () => { reader.nextField(); expect(reader.getFieldNumber()).toEqual(6); - expect(reader.readPackedFixed64String()).toEqual([ + expect((a.length = 0, reader.readPackableFixed64StringInto(a), a)).toEqual([ String(2 * 2 ** 32 + 1), String(3 * 2 ** 32 + 2), String(4 * 2 ** 32 + 3), ]); }); + it('writes packed 64bit string fields', () => { + const MAX_UINT64 = '18446744073709551615'; // 0xFFFFFFFFFFFFFFFF + const MAX_INT64 = '9223372036854775807'; // 0x7FFFFFFFFFFFFFFF + const MIN_INT64 = '-9223372036854775808'; // 0x8000000000000000 + const signedValues = ['0', '-1', MAX_INT64, MIN_INT64]; + const unsignedValues = ['0', '1', MAX_UINT64]; + const writer = new BinaryWriter(); + writer.writePackedSfixed64String(1, signedValues); + writer.writePackedInt64String(2, signedValues); + writer.writePackedSint64String(3, signedValues); + + writer.writePackedFixed64String(4, unsignedValues); + writer.writePackedUint64String(5, unsignedValues); + + const resultBuffer = writer.getResultBuffer(); + expect(toHexFields(resultBuffer)).toEqual({ + 1: '0a20' + + '0000000000000000' + // 0 + 'ffffffffffffffff' + // -1 + 'ffffffffffffff7f' + // MAX_INT64 + '0000000000000080', // MIN_INT64 + 2: '121e' + + '00' + // 0 + 'ffffffffffffffffff01' + // -1 + 'ffffffffffffffff7f' + // MAX_INT64 + '80808080808080808001', // MIN_INT64 + 3: '1a16' + + '00' + // 0 + '01' + // -1 + 'feffffffffffffffff01' + // MAX_INT64 + 'ffffffffffffffffff01', // MIN_INT64 + 4: '2218' + + '0000000000000000' + // 0 + '0100000000000000' + // 1 + 'ffffffffffffffff', // MAX_UINT64 + 5: '2a0c' + + '00' + // 0 + '01' + // 1 + 'ffffffffffffffffff01', // MAX_UINT64 + }); + const reader = BinaryReader.alloc(resultBuffer); + + reader.nextField(); + expect(reader.getFieldNumber()).toEqual(1); + const /** !Array */ results1 = []; + reader.readPackableSfixed64StringInto(results1); + expect(results1).toEqual(signedValues); + + reader.nextField(); + expect(reader.getFieldNumber()).toEqual(2); + const /** !Array */ results2 = []; + reader.readPackableInt64StringInto(results2); + expect(results2).toEqual(signedValues); + + reader.nextField(); + expect(reader.getFieldNumber()).toEqual(3); + const /** !Array */ results3 = []; + reader.readPackableSint64StringInto(results3); + expect(results3).toEqual(signedValues); + + reader.nextField(); + expect(reader.getFieldNumber()).toEqual(4); + const /** !Array */ results4 = []; + reader.readPackableFixed64StringInto(results4); + expect(results4).toEqual(unsignedValues); + + reader.nextField(); + expect(reader.getFieldNumber()).toEqual(5); + const /** !Array */ results5 = []; + reader.readPackableUint64StringInto(results5); + expect(results5).toEqual(unsignedValues); + }); + it('writes zigzag 64 fields', () => { - // Test cases directly from the protobuf dev guide. + // Test cases direcly from the protobuf dev guide. // https://engdoc.corp.google.com/eng/howto/protocolbuffers/developerguide/encoding.shtml?cl=head#types const testCases = [ {original: '0', zigzag: '0'}, @@ -258,58 +343,35 @@ describe('binaryWriterTest', () => { {original: '-9223372036854775808', zigzag: '18446744073709551615'}, ]; function decimalToLowBits(v) { - jspb.utils.splitDecimalString(v); - return jspb.utils.getSplit64Low() >>> 0; + splitDecimalString(v); + return getSplit64Low() >>> 0; } function decimalToHighBits(v) { - jspb.utils.splitDecimalString(v); - return jspb.utils.getSplit64High() >>> 0; + splitDecimalString(v); + return getSplit64High() >>> 0; } - const writer = new jspb.BinaryWriter(); - testCases.forEach(function(c) { + const writer = new BinaryWriter(); + testCases.forEach(c => { writer.writeSint64String(1, c.original); - writer.writeSintHash64(1, jspb.utils.decimalStringToHash64(c.original)); - jspb.utils.splitDecimalString(c.original); - writer.writeSplitZigzagVarint64( - 1, jspb.utils.getSplit64Low(), jspb.utils.getSplit64High()); + splitDecimalString(c.original); + writer.writeSplitZigzagVarint64(1, getSplit64Low(), getSplit64High()); }); - writer.writeRepeatedSint64String(2, testCases.map(function(c) { - return c.original; - })); - - writer.writeRepeatedSintHash64(3, testCases.map(function(c) { - return jspb.utils.decimalStringToHash64(c.original); - })); + writer.writeRepeatedSint64String(2, testCases.map(c => c.original)); writer.writeRepeatedSplitZigzagVarint64( - 4, testCases.map(function(c) { - return c.original; - }), - decimalToLowBits, decimalToHighBits); + 4, testCases.map(c => 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.writePackedSint64String(5, testCases.map(c => c.original)); writer.writePackedSplitZigzagVarint64( - 7, testCases.map(function(c) { - return c.original; - }), - decimalToLowBits, decimalToHighBits); + 7, testCases.map(c => c.original), decimalToLowBits, decimalToHighBits); // Verify by reading the stream as normal int64 fields and checking with // the canonical zigzag encoding of each value. - const reader = jspb.BinaryReader.alloc(writer.getResultBuffer()); - testCases.forEach(function(c) { - reader.nextField(); - expect(reader.getFieldNumber()).toEqual(1); - expect(reader.readUint64String()).toEqual(c.zigzag); + const reader = BinaryReader.alloc(writer.getResultBuffer()); + testCases.forEach(c => { reader.nextField(); expect(reader.getFieldNumber()).toEqual(1); expect(reader.readUint64String()).toEqual(c.zigzag); @@ -318,19 +380,13 @@ describe('binaryWriterTest', () => { expect(reader.readUint64String()).toEqual(c.zigzag); }); - testCases.forEach(function(c) { + testCases.forEach(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) { + testCases.forEach(c => { reader.nextField(); expect(reader.getFieldNumber()).toEqual(4); expect(reader.readUint64String()).toEqual(c.zigzag); @@ -338,36 +394,36 @@ describe('binaryWriterTest', () => { 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; - })); + const a = []; + expect((reader.readPackableUint64StringInto(a), a)) + .toEqual(testCases.map(c => c.zigzag)); reader.nextField(); expect(reader.getFieldNumber()).toEqual(7); - expect(reader.readPackedUint64String()).toEqual(testCases.map(function(c) { - return c.zigzag; - })); + expect((a.length = 0, reader.readPackableUint64StringInto(a), a)) + .toEqual(testCases.map(c => c.zigzag)); }); it('writes float32 fields', () => { const testCases = [ - 0, 1, -1, jspb.BinaryConstants.FLOAT32_MIN, - -jspb.BinaryConstants.FLOAT32_MIN, jspb.BinaryConstants.FLOAT32_MAX, - -jspb.BinaryConstants.FLOAT32_MAX, 3.1415927410125732, Infinity, - -Infinity, NaN + 0, + 1, + -1, + FLOAT32_MIN, + -FLOAT32_MIN, + FLOAT32_MAX, + -FLOAT32_MAX, + 3.1415927410125732, + Infinity, + -Infinity, + NaN, ]; - const writer = new jspb.BinaryWriter(); - testCases.forEach(function(f) { + const writer = new BinaryWriter(); + testCases.forEach(f => { writer.writeFloat(1, f); }); - const reader = jspb.BinaryReader.alloc(writer.getResultBuffer()); - testCases.forEach(function(f) { + const reader = BinaryReader.alloc(writer.getResultBuffer()); + testCases.forEach(f => { reader.nextField(); expect(reader.getFieldNumber()).toEqual(1); if (isNaN(f)) { @@ -380,17 +436,28 @@ describe('binaryWriterTest', () => { it('writes double fields', () => { const testCases = [ - 0, 1, -1, Number.MAX_SAFE_INTEGER, Number.MIN_SAFE_INTEGER, - Number.MAX_VALUE, Number.MIN_VALUE, jspb.BinaryConstants.FLOAT32_MIN, - -jspb.BinaryConstants.FLOAT32_MIN, jspb.BinaryConstants.FLOAT32_MAX, - -jspb.BinaryConstants.FLOAT32_MAX, Math.PI, Infinity, -Infinity, NaN + 0, + 1, + -1, + Number.MAX_SAFE_INTEGER, + Number.MIN_SAFE_INTEGER, + Number.MAX_VALUE, + Number.MIN_VALUE, + FLOAT32_MIN, + -FLOAT32_MIN, + FLOAT32_MAX, + -FLOAT32_MAX, + Math.PI, + Infinity, + -Infinity, + NaN, ]; - const writer = new jspb.BinaryWriter(); - testCases.forEach(function(f) { + const writer = new BinaryWriter(); + testCases.forEach(f => { writer.writeDouble(1, f); }); - const reader = jspb.BinaryReader.alloc(writer.getResultBuffer()); - testCases.forEach(function(f) { + const reader = BinaryReader.alloc(writer.getResultBuffer()); + testCases.forEach(f => { reader.nextField(); expect(reader.getFieldNumber()).toEqual(1); if (isNaN(f)) { @@ -400,4 +467,34 @@ describe('binaryWriterTest', () => { } }); }); + + it('writes binary fields', () => { + const testCases = [ + new Uint8Array([1, 2, 3]), + // Test that we don't stackoverflow with large blocks. + new Uint8Array(new Array(1000000).fill(1)), + '1234', + ByteString.fromBase64('5678'), + ByteString.fromUint8Array(new Uint8Array([9, 10])), + ]; + const writer = new BinaryWriter(); + testCases.forEach((f) => { + writer.writeBytes(1, f); + }); + const reader = BinaryReader.alloc(writer.getResultBuffer()); + testCases.forEach(f => { + reader.nextField(); + expect(reader.getFieldNumber()).toEqual(1); + expect(reader.readBytes()).toEqual(byteSourceToUint8Array(f)); + }); + }); }); + +/** + * @param {number} lowBits + * @param {number} highBits + * @return {!Array} + */ +function bitsAsArray(lowBits, highBits) { + return [lowBits >>> 0, highBits >>> 0]; +} diff --git a/bytestring.js b/bytestring.js new file mode 100644 index 0000000..0efdecd --- /dev/null +++ b/bytestring.js @@ -0,0 +1,257 @@ +/** + * @fileoverview ByteString class for encapsulating bytes fields. + */ + +goog.module('jspb.bytestring'); +goog.module.declareLegacyNamespace(); + +const { I_AM_INTERNAL, dataAsU8, encodeByteArray, uint8ArrayEquals } = goog.require('jspb.internal_bytes'); +const {assert, assertExists, assertInstanceof, assertNumber, assertString} = goog.require('goog.asserts'); +const {decodeUtf8, encodeUtf8} = goog.require('jspb.binary.utf8'); + +/** + * Encapsulation of a bytes field. + * + * Use the factory methods below to construct a ByteString. + * + * @final + * @struct + */ +class ByteString { + + /** + * Constructs a ByteString instance from a base64 string, per RFC 4648 section + * 4. + * @return {!ByteString} + */ + static fromBase64(/** string */ value) { + assertString(value); + return value ? new ByteString(value, I_AM_INTERNAL) : ByteString.empty(); + } + + /** + * Constructs a ByteString from a Uint8Array or Array of numbers. + * + * Makes a copy of the parameter. + * + * When passed an array of numbers, values will be truncated to be integers + * and then their value mod 2^8 will be preserved. + * + * See https://tc39.es/ecma262/multipage/abstract-operations.html#sec-touint8 + * + * @return {!ByteString} + */ + static fromUint8Array(/** !Uint8Array|!Array */ value) { + assert(value instanceof Uint8Array || Array.isArray(value)); + return value.length ? new ByteString(new Uint8Array(value), I_AM_INTERNAL) : + ByteString.empty(); + } + + /** + * Encodes `text` into a sequence of UTF-8 bytes and returns the result as a + * `ByteString`. + * @return {!ByteString} + */ + static fromStringUtf8(/** string */ text) { + assertString(text); + return text.length ? + new ByteString( + encodeUtf8(text, /* rejectUnpairedSurrogates=*/ true), + I_AM_INTERNAL) : + ByteString.empty(); + } + + /** + * Constructs a ByteString from a Blob. + * + * It is async because Blob does not provide sync access to its data. + * + * BROWSER COMPATIBILITY WARNING: + * This method uses Blob.arrayBuffer() to access Blob's content and therefore + * is compatible with browsers supporting this API, which is any release 2021 + * and later. See http://go/mdn/API/Blob/arrayBuffer for the full + * compatibility list. + * @return {!Promise} + */ + static async fromBlob(/** !Blob */ blob) { + assertInstanceof(blob, Blob); + if (blob.size === 0) return ByteString.empty(); + const data = await blob.arrayBuffer(); + return new ByteString(new Uint8Array(data), I_AM_INTERNAL); + } + + /** + * Returns the empty ByteString. + * @return {!ByteString} + */ + static empty() { + return emptyByteString || + (emptyByteString = new ByteString(null, I_AM_INTERNAL)); + } + + /** + * Returns this ByteString as a base64 encoded string, per RFC 4648 section 4. + * @return {string} + */ + asBase64() { + const value = this.value_; + if (value == null) { + return ''; + } + if (typeof value === 'string') { + return value; + } + return this.value_ = encodeByteArray(value); + } + + /** + * Returns this ByteString as a Uint8Array. This makes a copy and returns a + * new Uint8Array. + * @return {!Uint8Array} + */ + asUint8Array() { + return new Uint8Array(this.internalBytesUnsafe(I_AM_INTERNAL) || 0); + } + + /** + * Returns true if the ByteString is empty. + * @return {boolean} + */ + isEmpty() { + return this.value_ == null; + } + + /** + * Returns the size of the byte string in bytes. + * + * If you are only interested in whether or not the ByteString is empty, call + * `isEmpty` which is always faster. + * + * @return {number} + */ + sizeBytes() { + const bytes = this.internalBytesUnsafe(I_AM_INTERNAL); + return bytes ? bytes.length : 0; + } + + /** + * Returns the numeric value of the _unsigned_ byte at the given index. + * @return {number} + */ + unsignedByteAt(/** number */ index) { + assertNumber(index); + assert(index >= 0, 'index %s should be non-negative', index); + const bytes = this.internalBytesUnsafe(I_AM_INTERNAL); + assert( + index < bytes.length, 'index %s must be less than %s', index, + bytes.length); + return bytes[index]; + } + + /** + * Returns the numeric value of the byte at the given index as a _signed_ byte + * value in the range [-128,127] + * @return {number} + */ + signedByteAt(/** number */ index) { + const unsignedByte = this.unsignedByteAt(index); + // Bit operators are 'signed 32 bit' operators by default. + // First left shift so the sign-bit if it exists is in the 32 bit signed + // location + // Then, right shift back into the lower 8 bits to recover the now signed + // value. + return (unsignedByte << 24) >> 24; + } + + /** + * Returns a string by decoding the bytes as UTF-8. + * @param {{parsingErrorsAreFatal:boolean}=} opts an options bag. The + * `parsingErrorsAreFatal` option controls if invalid utf8 bytes should be + * a runtime error (if `true`) or if they should be replaced with the + * replacement character `\ufffd` (if `false`), the default is to throw. + * @return {string} + */ + asStringUtf8({parsingErrorsAreFatal = true} = {}) { + const bytes = this.internalBytesUnsafe(I_AM_INTERNAL); + return bytes ? decodeUtf8(bytes, 0, bytes.length, parsingErrorsAreFatal) : + ''; + } + + /** + * Returns the field as a Blob. This is a copy of the internal data. + * + * @param {?BlobPropertyBag=} options An object which may specify Blob + * properties. + * @return {!Blob} + */ + asBlob(options) { + const bytes = this.internalBytesUnsafe(I_AM_INTERNAL); + return bytes ? new Blob([bytes], options) : new Blob([], options); + } + + /** + * Internal only for access to the bytes in a zero copy fashion. + * + * See `unsafe_bytestring.js` for how to access this API. + * @param {*} areYouInternal + * @return {?Uint8Array} + * @package + */ + internalBytesUnsafe(areYouInternal) { + checkAllowedCaller(areYouInternal); + const u8 = dataAsU8(this.value_); + return (u8 == null) ? u8 : (this.value_ = u8); + } + + /** + * Internal only for access to the internals state of the bytestring, in a + * zero copy fashion. + * + * See `unsafe_bytestring.js` for how to access this API. + * @param {*} areYouInternal + * @return {string|!Uint8Array} + * @package + */ + internalUnwrap(areYouInternal) { + checkAllowedCaller(areYouInternal); + return this.value_ || ''; + } + + /** + * INTERNAL USE ONLY: Clients should use the factory functions above. + * @param {!Uint8Array|string|null} value Base64 string or Uint8Array. If + * null, this is an empty array. + * @param {*} areYouInternal + * @package + */ + constructor(value, areYouInternal) { + checkAllowedCaller(areYouInternal); + + /** + * This value is either a Uint8Array or a string, or else `null` for an + * empty byte string. + * + * @private {!Uint8Array|string|null} + */ + this.value_ = value; + + if (value != null && value.length === 0) { + throw new Error('ByteString should be constructed with non-empty values'); + } + } +} + + +/** @type {!ByteString|undefined} */ +let emptyByteString; + +/** + * @param {*} areYouInternal + */ +function checkAllowedCaller(areYouInternal) { + if (areYouInternal !== I_AM_INTERNAL) { + throw new Error('illegal external caller'); + } +} + +exports = {ByteString}; diff --git a/commonjs/export.js b/commonjs/export.js index 986a481..61e5390 100644 --- a/commonjs/export.js +++ b/commonjs/export.js @@ -14,6 +14,7 @@ goog.require('jspb.BinaryReader'); goog.require('jspb.BinaryWriter'); goog.require('jspb.ExtensionFieldBinaryInfo'); goog.require('jspb.ExtensionFieldInfo'); +goog.require('jspb.internal.public_for_gencode'); goog.require('jspb.Message'); goog.require('jspb.Map'); @@ -27,6 +28,8 @@ if (typeof exports === 'object') { exports['BinaryWriter'] = jspb.BinaryWriter; exports['ExtensionFieldInfo'] = jspb.ExtensionFieldInfo; exports['ExtensionFieldBinaryInfo'] = jspb.ExtensionFieldBinaryInfo; + exports['internal'] = { public_for_gencode: jspb.internal.public_for_gencode }; + // These are used by generated code but should not be used directly by clients. exports['exportSymbol'] = goog.exportSymbol; diff --git a/commonjs/export_testdeps.js b/commonjs/export_testdeps.js index 7bb5690..8c2b4a8 100644 --- a/commonjs/export_testdeps.js +++ b/commonjs/export_testdeps.js @@ -12,13 +12,18 @@ goog.provide('jspb.ExportTestDeps'); goog.require('goog.crypt.base64'); goog.require('goog.testing.PropertyReplacer'); +goog.require('jspb.arith'); goog.require('jspb.debug'); goog.require('jspb.BinaryReader'); goog.require('jspb.BinaryWriter'); +goog.require('jspb.BinaryConstants'); goog.require('jspb.ExtensionFieldBinaryInfo'); goog.require('jspb.ExtensionFieldInfo'); goog.require('jspb.Message'); goog.require('jspb.Map'); +goog.require('jspb.utils'); +goog.require('jspb.binary.utf8'); +goog.require('jspb.binary.test_utils'); if (typeof exports === 'object') { exports['goog'] = { @@ -46,6 +51,50 @@ if (typeof exports === 'object') { }; exports['jspb'] = { + 'arith': { + 'Int64': jspb.arith.Int64, + 'UInt64': jspb.arith.UInt64, + }, + 'binary': { + 'decoder': { + 'BinaryDecoder': jspb.binary.decoder.BinaryDecoder, + }, + 'encoder': { + 'BinaryEncoder': jspb.binary.encoder.BinaryEncoder, + }, + 'utf8': { + 'encodeUtf8': jspb.binary.utf8.encodeUtf8, + }, + 'test_utils': { + 'toHexFields': jspb.binary.test_utils.toHexFields, + }, + }, + 'bytestring': { + 'ByteString': jspb.bytestring.ByteString, + }, + 'utils': { + 'makeTag': jspb.utils.makeTag, + 'countFixed32Fields': jspb.utils.countFixed32Fields, + 'countFixed64Fields': jspb.utils.countFixed64Fields, + 'splitDecimalString': jspb.utils.splitDecimalString, + 'byteSourceToUint8Array': jspb.utils.byteSourceToUint8Array, + 'getSplit64Low': jspb.utils.getSplit64Low, + 'getSplit64High': jspb.utils.getSplit64High, + 'countVarintFields': jspb.utils.countVarintFields, + 'countDelimitedFields': jspb.utils.countDelimitedFields, + 'countVarints': jspb.utils.countVarints, + 'toZigzag64': jspb.utils.toZigzag64, + 'fromZigzag64': jspb.utils.fromZigzag64, + 'splitFloat64': jspb.utils.splitFloat64, + 'joinFloat64': jspb.utils.joinFloat64, + 'joinFloat32': jspb.utils.joinFloat32, + 'splitFloat32': jspb.utils.splitFloat32, + 'joinUnsignedDecimalString': jspb.utils.joinUnsignedDecimalString, + 'sliceUint8Array': jspb.utils.sliceUint8Array, + 'joinSignedDecimalString': jspb.utils.joinSignedDecimalString, + }, + 'BinaryConstants': jspb.BinaryConstants, + 'debug': jspb.debug, 'BinaryReader': jspb.BinaryReader, 'BinaryWriter': jspb.BinaryWriter, diff --git a/commonjs/rewrite_tests_for_commonjs.js b/commonjs/rewrite_tests_for_commonjs.js index 4487c42..8b13c28 100644 --- a/commonjs/rewrite_tests_for_commonjs.js +++ b/commonjs/rewrite_tests_for_commonjs.js @@ -71,7 +71,13 @@ console.log("global.goog = testdeps.goog;"); console.log("global.jspb = googleProtobuf.jspb;"); console.log(""); -lineReader.on('line', function(line) { +lineReader.on('line', function (line) { + const isModuleGet = line.match(/(.*)goog\.module\.get\('([^']*)'\)([^;]*);/); + if (isModuleGet) { + const fullSym = isModuleGet[2]; + console.log(isModuleGet[1] + "testdeps." + fullSym + isModuleGet[3]); + return; + } var isRequire = line.match(/goog\.require\('([^']*)'\)/); var isLoadFromFile = line.match(/CommonJS-LoadFromFile: (\S*) (.*)/); var isSetTestOnly = line.match(/goog.setTestOnly()/); diff --git a/conformance/LICENSE.md b/conformance/LICENSE.md new file mode 100644 index 0000000..0890b3f --- /dev/null +++ b/conformance/LICENSE.md @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2022, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. 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. + +3. Neither the name of the copyright holder 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 HOLDER 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. diff --git a/conformance/failing_tests.txt b/conformance/failing_tests.txt new file mode 100644 index 0000000..91b9dfd --- /dev/null +++ b/conformance/failing_tests.txt @@ -0,0 +1,48 @@ +Recommended.Editions_Proto2.ProtobufInput.ValidDataOneofBinary.MESSAGE.Merge.ProtobufOutput # Output was not equivalent to reference message: Expect: \202\007\014\022\012\010\001\020\001\310\005\001\310\005\001, but got: \ +Recommended.Editions_Proto3.ProtobufInput.ValidDataOneofBinary.MESSAGE.Merge.ProtobufOutput # Output was not equivalent to reference message: Expect: \202\007\014\022\012\010\001\020\001\310\005\001\310\005\001, but got: \ +Recommended.Proto2.ProtobufInput.ValidDataOneofBinary.MESSAGE.Merge.ProtobufOutput # Output was not equivalent to reference message: Expect: \202\007\014\022\012\010\001\020\001\310\005\001\310\005\001, but got: \ +Recommended.Proto2.ProtobufInput.ValidMessageSetEncoding.SubmessageEncoding.NotUnknown.ProtobufOutput # Output was not equivalent to reference message: added: message_set_correct.(protobuf_test_messages.proto2.TestAllTypesProto2.Ext +Recommended.Proto3.ProtobufInput.ValidDataOneofBinary.MESSAGE.Merge.ProtobufOutput # Output was not equivalent to reference message: Expect: \202\007\014\022\012\010\001\020\001\310\005\001\310\005\001, but got: \ +Required.Editions.ProtobufInput.ValidDelimitedExtension.GroupLike.ProtobufOutput # Failed to parse input or produce output. +Required.Editions.ProtobufInput.ValidDelimitedExtension.NotGroupLike.ProtobufOutput # Failed to parse input or produce output. +Required.Editions.ProtobufInput.ValidDelimitedField.GroupLike.ProtobufOutput # Output was not equivalent to reference message: added: delimited_field: { group_int32: 99 } +Required.Editions.ProtobufInput.ValidDelimitedField.NotGroupLike.ProtobufOutput # Output was not equivalent to reference message: added: groupliketype: { group_int32: 99 } +Required.Editions_Proto2.ProtobufInput.RepeatedScalarMessageMerge.ProtobufOutput # Output was not equivalent to reference message: deleted: optional_nested_message.corecursive.optional_int64: 1234modified: optio +Required.Editions_Proto2.ProtobufInput.UnknownOrdering.ProtobufOutput # Unknown field mismatch +Required.Editions_Proto2.ProtobufInput.UnknownVarint.ProtobufOutput # Output was not equivalent to reference message: Expect: \250\037\001, but got: +Required.Editions_Proto2.ProtobufInput.UnmatchedEndGroup # Should have failed to parse, but didn't. +Required.Editions_Proto2.ProtobufInput.UnmatchedEndGroupNestedLen # Should have failed to parse, but didn't. +Required.Editions_Proto2.ProtobufInput.UnmatchedEndGroupUnknown # Should have failed to parse, but didn't. +Required.Editions_Proto2.ProtobufInput.UnmatchedEndGroupWithData # Should have failed to parse, but didn't. +Required.Editions_Proto2.ProtobufInput.UnmatchedEndGroupWrongType # Should have failed to parse, but didn't. +Required.Editions_Proto2.ProtobufInput.ValidDataOneof.MESSAGE.Merge.ProtobufOutput # Output was not equivalent to reference message: deleted: oneof_nested_message.corecursive.optional_int32: 1deleted: oneof_nested +Required.Editions_Proto3.ProtobufInput.RepeatedScalarMessageMerge.ProtobufOutput # Output was not equivalent to reference message: deleted: optional_nested_message.corecursive.optional_int64: 1234modified: optio +Required.Editions_Proto3.ProtobufInput.UnknownOrdering.ProtobufOutput # Unknown field mismatch +Required.Editions_Proto3.ProtobufInput.UnknownVarint.ProtobufOutput # Output was not equivalent to reference message: Expect: \250\037\001, but got: +Required.Editions_Proto3.ProtobufInput.UnmatchedEndGroup # Should have failed to parse, but didn't. +Required.Editions_Proto3.ProtobufInput.UnmatchedEndGroupNestedLen # Should have failed to parse, but didn't. +Required.Editions_Proto3.ProtobufInput.UnmatchedEndGroupUnknown # Should have failed to parse, but didn't. +Required.Editions_Proto3.ProtobufInput.UnmatchedEndGroupWithData # Should have failed to parse, but didn't. +Required.Editions_Proto3.ProtobufInput.UnmatchedEndGroupWrongType # Should have failed to parse, but didn't. +Required.Editions_Proto3.ProtobufInput.ValidDataOneof.MESSAGE.Merge.ProtobufOutput # Output was not equivalent to reference message: deleted: oneof_nested_message.corecursive.optional_int32: 1deleted: oneof_nested +Required.Proto2.ProtobufInput.MessageSetEncoding.UnknownExtension.ProtobufOutput # Output was not equivalent to reference message: deleted: message_set_correct.4135300[0]: "\000c" +Required.Proto2.ProtobufInput.RepeatedScalarMessageMerge.ProtobufOutput # Output was not equivalent to reference message: deleted: optional_nested_message.corecursive.optional_int64: 1234modified: optio +Required.Proto2.ProtobufInput.UnknownOrdering.ProtobufOutput # Unknown field mismatch +Required.Proto2.ProtobufInput.UnknownVarint.ProtobufOutput # Output was not equivalent to reference message: Expect: \250\037\001, but got: +Required.Proto2.ProtobufInput.UnmatchedEndGroup # Should have failed to parse, but didn't. +Required.Proto2.ProtobufInput.UnmatchedEndGroupNestedLen # Should have failed to parse, but didn't. +Required.Proto2.ProtobufInput.UnmatchedEndGroupUnknown # Should have failed to parse, but didn't. +Required.Proto2.ProtobufInput.UnmatchedEndGroupWithData # Should have failed to parse, but didn't. +Required.Proto2.ProtobufInput.UnmatchedEndGroupWrongType # Should have failed to parse, but didn't. +Required.Proto2.ProtobufInput.ValidDataOneof.MESSAGE.Merge.ProtobufOutput # Output was not equivalent to reference message: deleted: oneof_nested_message.corecursive.optional_int32: 1deleted: oneof_nested +Required.Proto2.ProtobufInput.ValidMessageSetEncoding.OutOfOrderGroupsEntries.ProtobufOutput # Output was not equivalent to reference message: deleted: message_set_correct.(protobuf_test_messages.proto2.TestAllTypesProto2.M +Required.Proto2.ProtobufInput.ValidMessageSetEncoding.ProtobufOutput # Output was not equivalent to reference message: deleted: message_set_correct.(protobuf_test_messages.proto2.TestAllTypesProto2.M +Required.Proto3.ProtobufInput.RepeatedScalarMessageMerge.ProtobufOutput # Output was not equivalent to reference message: deleted: optional_nested_message.corecursive.optional_int64: 1234modified: optio +Required.Proto3.ProtobufInput.UnknownOrdering.ProtobufOutput # Unknown field mismatch +Required.Proto3.ProtobufInput.UnknownVarint.ProtobufOutput # Output was not equivalent to reference message: Expect: \250\037\001, but got: +Required.Proto3.ProtobufInput.UnmatchedEndGroup # Should have failed to parse, but didn't. +Required.Proto3.ProtobufInput.UnmatchedEndGroupNestedLen # Should have failed to parse, but didn't. +Required.Proto3.ProtobufInput.UnmatchedEndGroupUnknown # Should have failed to parse, but didn't. +Required.Proto3.ProtobufInput.UnmatchedEndGroupWithData # Should have failed to parse, but didn't. +Required.Proto3.ProtobufInput.UnmatchedEndGroupWrongType # Should have failed to parse, but didn't. +Required.Proto3.ProtobufInput.ValidDataOneof.MESSAGE.Merge.ProtobufOutput # Output was not equivalent to reference message: deleted: oneof_nested_message.corecursive.optional_int32: 1deleted: oneof_nested diff --git a/conformance/package.json b/conformance/package.json new file mode 100644 index 0000000..5a68739 --- /dev/null +++ b/conformance/package.json @@ -0,0 +1,18 @@ +{ + "name": "google-protobuf-conformance", + "private": true, + "version": "0.0.1", + "description": "Conformance test runner for google-protobuf", + "main": "runner.js", + "files": [ + "runner.js", + "protos/*.proto", + "package.json", + "LICENSE.md" + ], + "dependencies": { + "google-protobuf": "file:../google-protobuf-3.21.4.tgz" + }, + "author": "Google Protocol Buffers Team", + "license": "BSD-3-Clause" +} diff --git a/conformance/protos/conformance.proto b/conformance/protos/conformance.proto new file mode 100644 index 0000000..e3298f0 --- /dev/null +++ b/conformance/protos/conformance.proto @@ -0,0 +1,172 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +syntax = "proto3"; + +package conformance; + +option java_package = "com.google.protobuf.conformance"; +option objc_class_prefix = "Conformance"; + +// This defines the conformance testing protocol. This protocol exists between +// the conformance test suite itself and the code being tested. For each test, +// the suite will send a ConformanceRequest message and expect a +// ConformanceResponse message. +// +// You can either run the tests in two different ways: +// +// 1. in-process (using the interface in conformance_test.h). +// +// 2. as a sub-process communicating over a pipe. Information about how to +// do this is in conformance_test_runner.cc. +// +// Pros/cons of the two approaches: +// +// - running as a sub-process is much simpler for languages other than C/C++. +// +// - running as a sub-process may be more tricky in unusual environments like +// iOS apps, where fork/stdin/stdout are not available. + +enum WireFormat { + UNSPECIFIED = 0; + PROTOBUF = 1; + JSON = 2; + JSPB = 3; // Only used inside Google. Opensource testees just skip it. + TEXT_FORMAT = 4; +} + +enum TestCategory { + UNSPECIFIED_TEST = 0; + BINARY_TEST = 1; // Test binary wire format. + JSON_TEST = 2; // Test json wire format. + // Similar to JSON_TEST. However, during parsing json, testee should ignore + // unknown fields. This feature is optional. Each implementation can decide + // whether to support it. See + // https://developers.google.com/protocol-buffers/docs/proto3#json_options + // for more detail. + JSON_IGNORE_UNKNOWN_PARSING_TEST = 3; + // Test jspb wire format. Only used inside Google. Opensource testees just + // skip it. + JSPB_TEST = 4; + // Test text format. For cpp, java and python, testees can already deal with + // this type. Testees of other languages can simply skip it. + TEXT_FORMAT_TEST = 5; +} + +// Meant to encapsulate all types of tests: successes, skips, failures, etc. +// Therefore, this may or may not have a failure message. Failure messages +// may be truncated for our failure lists. +message TestStatus { + string name = 1; + string failure_message = 2; + // What an actual test name matched to in a failure list. Can be wildcarded or + // an exact match without wildcards. + string matched_name = 3; +} + +// The conformance runner will request a list of failures as the first request. +// This will be known by message_type == "conformance.FailureSet", a conformance +// test should return a serialized FailureSet in protobuf_payload. +message FailureSet { + repeated TestStatus test = 2; + reserved 1; +} + +// Represents a single test case's input. The testee should: +// +// 1. parse this proto (which should always succeed) +// 2. parse the protobuf or JSON payload in "payload" (which may fail) +// 3. if the parse succeeded, serialize the message in the requested format. +message ConformanceRequest { + // The payload (whether protobuf of JSON) is always for a + // protobuf_test_messages.proto3.TestAllTypes proto (as defined in + // src/google/protobuf/proto3_test_messages.proto). + oneof payload { + bytes protobuf_payload = 1; + string json_payload = 2; + // Only used inside Google. Opensource testees just skip it. + string jspb_payload = 7; + string text_payload = 8; + } + + // Which format should the testee serialize its message to? + WireFormat requested_output_format = 3; + + // The full name for the test message to use; for the moment, either: + // protobuf_test_messages.proto3.TestAllTypesProto3 or + // protobuf_test_messages.proto2.TestAllTypesProto2 or + // protobuf_test_messages.editions.proto2.TestAllTypesProto2 or + // protobuf_test_messages.editions.proto3.TestAllTypesProto3 or + // protobuf_test_messages.editions.TestAllTypesEdition2023. + string message_type = 4; + + // Each test is given a specific test category. Some category may need + // specific support in testee programs. Refer to the definition of + // TestCategory for more information. + TestCategory test_category = 5; + + // Specify details for how to encode jspb. + JspbEncodingConfig jspb_encoding_options = 6; + + // This can be used in json and text format. If true, testee should print + // unknown fields instead of ignore. This feature is optional. + bool print_unknown_fields = 9; +} + +// Represents a single test case's output. +message ConformanceResponse { + oneof result { + // This string should be set to indicate parsing failed. The string can + // provide more information about the parse error if it is available. + // + // Setting this string does not necessarily mean the testee failed the + // test. Some of the test cases are intentionally invalid input. + string parse_error = 1; + + // If the input was successfully parsed but errors occurred when + // serializing it to the requested output format, set the error message in + // this field. + string serialize_error = 6; + + // This should be set if the test program timed out. The string should + // provide more information about what the child process was doing when it + // was killed. + string timeout_error = 9; + + // This should be set if some other error occurred. This will always + // indicate that the test failed. The string can provide more information + // about the failure. + string runtime_error = 2; + + // If the input was successfully parsed and the requested output was + // protobuf, serialize it to protobuf and set it in this field. + bytes protobuf_payload = 3; + + // If the input was successfully parsed and the requested output was JSON, + // serialize to JSON and set it in this field. + string json_payload = 4; + + // For when the testee skipped the test, likely because a certain feature + // wasn't supported, like JSON input/output. + string skipped = 5; + + // If the input was successfully parsed and the requested output was JSPB, + // serialize to JSPB and set it in this field. JSPB is only used inside + // Google. Opensource testees can just skip it. + string jspb_payload = 7; + + // If the input was successfully parsed and the requested output was + // TEXT_FORMAT, serialize to TEXT_FORMAT and set it in this field. + string text_payload = 8; + } +} + +// Encoding options for jspb format. +message JspbEncodingConfig { + // Encode the value field of Any as jspb array if true, otherwise binary. + bool use_jspb_array_any_format = 1; +} diff --git a/conformance/protos/test_messages_edition2023.proto b/conformance/protos/test_messages_edition2023.proto new file mode 100644 index 0000000..7affff6 --- /dev/null +++ b/conformance/protos/test_messages_edition2023.proto @@ -0,0 +1,217 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2024 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +edition = "2023"; + +package protobuf_test_messages.editions; + +option features.message_encoding = DELIMITED; +option java_package = "com.google.protobuf_test_messages.edition2023"; +option java_multiple_files = true; +option objc_class_prefix = "Editions"; + +message ComplexMessage { + int32 d = 1; +} + +message TestAllTypesEdition2023 { + message NestedMessage { + int32 a = 1; + TestAllTypesEdition2023 corecursive = 2 + [features.message_encoding = LENGTH_PREFIXED]; + } + + enum NestedEnum { + FOO = 0; + BAR = 1; + BAZ = 2; + NEG = -1; // Intentionally negative. + } + + // Singular + int32 optional_int32 = 1; + int64 optional_int64 = 2; + uint32 optional_uint32 = 3; + uint64 optional_uint64 = 4; + sint32 optional_sint32 = 5; + sint64 optional_sint64 = 6; + fixed32 optional_fixed32 = 7; + fixed64 optional_fixed64 = 8; + sfixed32 optional_sfixed32 = 9; + sfixed64 optional_sfixed64 = 10; + float optional_float = 11; + double optional_double = 12; + bool optional_bool = 13; + string optional_string = 14; + bytes optional_bytes = 15; + + NestedMessage optional_nested_message = 18 + [features.message_encoding = LENGTH_PREFIXED]; + ForeignMessageEdition2023 optional_foreign_message = 19 + [features.message_encoding = LENGTH_PREFIXED]; + + NestedEnum optional_nested_enum = 21; + ForeignEnumEdition2023 optional_foreign_enum = 22; + + string optional_string_piece = 24 [ctype = STRING_PIECE]; + string optional_cord = 25 [ctype = CORD]; + + TestAllTypesEdition2023 recursive_message = 27 + [features.message_encoding = LENGTH_PREFIXED]; + + // Repeated + repeated int32 repeated_int32 = 31; + repeated int64 repeated_int64 = 32; + repeated uint32 repeated_uint32 = 33; + repeated uint64 repeated_uint64 = 34; + repeated sint32 repeated_sint32 = 35; + repeated sint64 repeated_sint64 = 36; + repeated fixed32 repeated_fixed32 = 37; + repeated fixed64 repeated_fixed64 = 38; + repeated sfixed32 repeated_sfixed32 = 39; + repeated sfixed64 repeated_sfixed64 = 40; + repeated float repeated_float = 41; + repeated double repeated_double = 42; + repeated bool repeated_bool = 43; + repeated string repeated_string = 44; + repeated bytes repeated_bytes = 45; + + repeated NestedMessage repeated_nested_message = 48 + [features.message_encoding = LENGTH_PREFIXED]; + repeated ForeignMessageEdition2023 repeated_foreign_message = 49 + [features.message_encoding = LENGTH_PREFIXED]; + + repeated NestedEnum repeated_nested_enum = 51; + repeated ForeignEnumEdition2023 repeated_foreign_enum = 52; + + repeated string repeated_string_piece = 54 [ctype = STRING_PIECE]; + repeated string repeated_cord = 55 [ctype = CORD]; + + // Packed + repeated int32 packed_int32 = 75 [features.repeated_field_encoding = PACKED]; + repeated int64 packed_int64 = 76 [features.repeated_field_encoding = PACKED]; + repeated uint32 packed_uint32 = 77 + [features.repeated_field_encoding = PACKED]; + repeated uint64 packed_uint64 = 78 + [features.repeated_field_encoding = PACKED]; + repeated sint32 packed_sint32 = 79 + [features.repeated_field_encoding = PACKED]; + repeated sint64 packed_sint64 = 80 + [features.repeated_field_encoding = PACKED]; + repeated fixed32 packed_fixed32 = 81 + [features.repeated_field_encoding = PACKED]; + repeated fixed64 packed_fixed64 = 82 + [features.repeated_field_encoding = PACKED]; + repeated sfixed32 packed_sfixed32 = 83 + [features.repeated_field_encoding = PACKED]; + repeated sfixed64 packed_sfixed64 = 84 + [features.repeated_field_encoding = PACKED]; + repeated float packed_float = 85 [features.repeated_field_encoding = PACKED]; + repeated double packed_double = 86 + [features.repeated_field_encoding = PACKED]; + repeated bool packed_bool = 87 [features.repeated_field_encoding = PACKED]; + repeated NestedEnum packed_nested_enum = 88 + [features.repeated_field_encoding = PACKED]; + + // Unpacked + repeated int32 unpacked_int32 = 89 + [features.repeated_field_encoding = EXPANDED]; + repeated int64 unpacked_int64 = 90 + [features.repeated_field_encoding = EXPANDED]; + repeated uint32 unpacked_uint32 = 91 + [features.repeated_field_encoding = EXPANDED]; + repeated uint64 unpacked_uint64 = 92 + [features.repeated_field_encoding = EXPANDED]; + repeated sint32 unpacked_sint32 = 93 + [features.repeated_field_encoding = EXPANDED]; + repeated sint64 unpacked_sint64 = 94 + [features.repeated_field_encoding = EXPANDED]; + repeated fixed32 unpacked_fixed32 = 95 + [features.repeated_field_encoding = EXPANDED]; + repeated fixed64 unpacked_fixed64 = 96 + [features.repeated_field_encoding = EXPANDED]; + repeated sfixed32 unpacked_sfixed32 = 97 + [features.repeated_field_encoding = EXPANDED]; + repeated sfixed64 unpacked_sfixed64 = 98 + [features.repeated_field_encoding = EXPANDED]; + repeated float unpacked_float = 99 + [features.repeated_field_encoding = EXPANDED]; + repeated double unpacked_double = 100 + [features.repeated_field_encoding = EXPANDED]; + repeated bool unpacked_bool = 101 + [features.repeated_field_encoding = EXPANDED]; + repeated NestedEnum unpacked_nested_enum = 102 + [features.repeated_field_encoding = EXPANDED]; + + // Map + map map_int32_int32 = 56; + map map_int64_int64 = 57; + map map_uint32_uint32 = 58; + map map_uint64_uint64 = 59; + map map_sint32_sint32 = 60; + map map_sint64_sint64 = 61; + map map_fixed32_fixed32 = 62; + map map_fixed64_fixed64 = 63; + map map_sfixed32_sfixed32 = 64; + map map_sfixed64_sfixed64 = 65; + map map_int32_float = 66; + map map_int32_double = 67; + map map_bool_bool = 68; + map map_string_string = 69; + map map_string_bytes = 70; + map map_string_nested_message = 71; + map map_string_foreign_message = 72; + map map_string_nested_enum = 73; + map map_string_foreign_enum = 74; + + oneof oneof_field { + uint32 oneof_uint32 = 111; + NestedMessage oneof_nested_message = 112 + [features.message_encoding = LENGTH_PREFIXED]; + string oneof_string = 113; + bytes oneof_bytes = 114; + bool oneof_bool = 115; + uint64 oneof_uint64 = 116; + float oneof_float = 117; + double oneof_double = 118; + NestedEnum oneof_enum = 119; + } + + // extensions + extensions 120 to 200; + + // groups + message GroupLikeType { + int32 group_int32 = 202; + uint32 group_uint32 = 203; + } + GroupLikeType groupliketype = 201; + GroupLikeType delimited_field = 202; +} + +message ForeignMessageEdition2023 { + int32 c = 1; +} + +enum ForeignEnumEdition2023 { + FOREIGN_FOO = 0; + FOREIGN_BAR = 1; + FOREIGN_BAZ = 2; +} + +extend TestAllTypesEdition2023 { + int32 extension_int32 = 120; +} + +message GroupLikeType { + int32 c = 1; +} + +extend TestAllTypesEdition2023 { + GroupLikeType groupliketype = 121; + GroupLikeType delimited_ext = 122; +} diff --git a/conformance/protos/test_messages_proto2.proto b/conformance/protos/test_messages_proto2.proto new file mode 100644 index 0000000..097e812 --- /dev/null +++ b/conformance/protos/test_messages_proto2.proto @@ -0,0 +1,416 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd +// +// Test schema for proto2 messages. This test schema is used by: +// +// - conformance tests +// + +// LINT: ALLOW_GROUPS + +syntax = "proto2"; + +package protobuf_test_messages.proto2; + +option java_package = "com.google.protobuf_test_messages.proto2"; +option objc_class_prefix = "Proto2"; + +// This is the default, but we specify it here explicitly. +option optimize_for = SPEED; + +option cc_enable_arenas = true; + + +// This proto includes every type of field in both singular and repeated +// forms. +// +// Also, crucially, all messages and enums in this file are eventually +// submessages of this message. So for example, a fuzz test of TestAllTypes +// could trigger bugs that occur in any message type in this file. We verify +// this stays true in a unit test. +message TestAllTypesProto2 { + message NestedMessage { + optional int32 a = 1; + optional TestAllTypesProto2 corecursive = 2; + } + + enum NestedEnum { + FOO = 0; + BAR = 1; + BAZ = 2; + NEG = -1; // Intentionally negative. + } + + // Singular + optional int32 optional_int32 = 1; + optional int64 optional_int64 = 2; + optional uint32 optional_uint32 = 3; + optional uint64 optional_uint64 = 4; + optional sint32 optional_sint32 = 5; + optional sint64 optional_sint64 = 6; + optional fixed32 optional_fixed32 = 7; + optional fixed64 optional_fixed64 = 8; + optional sfixed32 optional_sfixed32 = 9; + optional sfixed64 optional_sfixed64 = 10; + optional float optional_float = 11; + optional double optional_double = 12; + optional bool optional_bool = 13; + optional string optional_string = 14; + optional bytes optional_bytes = 15; + + optional NestedMessage optional_nested_message = 18; + optional ForeignMessageProto2 optional_foreign_message = 19; + + optional NestedEnum optional_nested_enum = 21; + optional ForeignEnumProto2 optional_foreign_enum = 22; + + optional string optional_string_piece = 24 [ctype = STRING_PIECE]; + optional string optional_cord = 25 [ctype = CORD]; + + optional TestAllTypesProto2 recursive_message = 27; + + // Repeated + repeated int32 repeated_int32 = 31; + repeated int64 repeated_int64 = 32; + repeated uint32 repeated_uint32 = 33; + repeated uint64 repeated_uint64 = 34; + repeated sint32 repeated_sint32 = 35; + repeated sint64 repeated_sint64 = 36; + repeated fixed32 repeated_fixed32 = 37; + repeated fixed64 repeated_fixed64 = 38; + repeated sfixed32 repeated_sfixed32 = 39; + repeated sfixed64 repeated_sfixed64 = 40; + repeated float repeated_float = 41; + repeated double repeated_double = 42; + repeated bool repeated_bool = 43; + repeated string repeated_string = 44; + repeated bytes repeated_bytes = 45; + + repeated NestedMessage repeated_nested_message = 48; + repeated ForeignMessageProto2 repeated_foreign_message = 49; + + repeated NestedEnum repeated_nested_enum = 51; + repeated ForeignEnumProto2 repeated_foreign_enum = 52; + + repeated string repeated_string_piece = 54 [ctype = STRING_PIECE]; + repeated string repeated_cord = 55 [ctype = CORD]; + + // Packed + repeated int32 packed_int32 = 75 [packed = true]; + repeated int64 packed_int64 = 76 [packed = true]; + repeated uint32 packed_uint32 = 77 [packed = true]; + repeated uint64 packed_uint64 = 78 [packed = true]; + repeated sint32 packed_sint32 = 79 [packed = true]; + repeated sint64 packed_sint64 = 80 [packed = true]; + repeated fixed32 packed_fixed32 = 81 [packed = true]; + repeated fixed64 packed_fixed64 = 82 [packed = true]; + repeated sfixed32 packed_sfixed32 = 83 [packed = true]; + repeated sfixed64 packed_sfixed64 = 84 [packed = true]; + repeated float packed_float = 85 [packed = true]; + repeated double packed_double = 86 [packed = true]; + repeated bool packed_bool = 87 [packed = true]; + repeated NestedEnum packed_nested_enum = 88 [packed = true]; + + // Unpacked + repeated int32 unpacked_int32 = 89 [packed = false]; + repeated int64 unpacked_int64 = 90 [packed = false]; + repeated uint32 unpacked_uint32 = 91 [packed = false]; + repeated uint64 unpacked_uint64 = 92 [packed = false]; + repeated sint32 unpacked_sint32 = 93 [packed = false]; + repeated sint64 unpacked_sint64 = 94 [packed = false]; + repeated fixed32 unpacked_fixed32 = 95 [packed = false]; + repeated fixed64 unpacked_fixed64 = 96 [packed = false]; + repeated sfixed32 unpacked_sfixed32 = 97 [packed = false]; + repeated sfixed64 unpacked_sfixed64 = 98 [packed = false]; + repeated float unpacked_float = 99 [packed = false]; + repeated double unpacked_double = 100 [packed = false]; + repeated bool unpacked_bool = 101 [packed = false]; + repeated NestedEnum unpacked_nested_enum = 102 [packed = false]; + + // Map + map map_int32_int32 = 56; + map map_int64_int64 = 57; + map map_uint32_uint32 = 58; + map map_uint64_uint64 = 59; + map map_sint32_sint32 = 60; + map map_sint64_sint64 = 61; + map map_fixed32_fixed32 = 62; + map map_fixed64_fixed64 = 63; + map map_sfixed32_sfixed32 = 64; + map map_sfixed64_sfixed64 = 65; + map map_int32_bool = 104; + map map_int32_float = 66; + map map_int32_double = 67; + map map_int32_nested_message = 103; + map map_bool_bool = 68; + map map_string_string = 69; + map map_string_bytes = 70; + map map_string_nested_message = 71; + map map_string_foreign_message = 72; + map map_string_nested_enum = 73; + map map_string_foreign_enum = 74; + + oneof oneof_field { + uint32 oneof_uint32 = 111; + NestedMessage oneof_nested_message = 112; + string oneof_string = 113; + bytes oneof_bytes = 114; + bool oneof_bool = 115; + uint64 oneof_uint64 = 116; + float oneof_float = 117; + double oneof_double = 118; + NestedEnum oneof_enum = 119; + } + + // extensions + extensions 120 to 200; + + // groups + optional group Data = 201 { + optional int32 group_int32 = 202; + optional uint32 group_uint32 = 203; + } + + optional group MultiWordGroupField = 204 { + optional int32 group_int32 = 205; + optional uint32 group_uint32 = 206; + } + + // default values + optional int32 default_int32 = 241 [default = -123456789]; + optional int64 default_int64 = 242 [default = -9123456789123456789]; + optional uint32 default_uint32 = 243 [default = 2123456789]; + optional uint64 default_uint64 = 244 [default = 10123456789123456789]; + optional sint32 default_sint32 = 245 [default = -123456789]; + optional sint64 default_sint64 = 246 [default = -9123456789123456789]; + optional fixed32 default_fixed32 = 247 [default = 2123456789]; + optional fixed64 default_fixed64 = 248 [default = 10123456789123456789]; + optional sfixed32 default_sfixed32 = 249 [default = -123456789]; + optional sfixed64 default_sfixed64 = 250 [default = -9123456789123456789]; + optional float default_float = 251 [default = 9e9]; + optional double default_double = 252 [default = 7e22]; + optional bool default_bool = 253 [default = true]; + optional string default_string = 254 [default = "Rosebud"]; + optional bytes default_bytes = 255 [default = "joshua"]; + + // Test field-name-to-JSON-name convention. + // (protobuf says names can be any valid C/C++ identifier.) + optional int32 fieldname1 = 401; + optional int32 field_name2 = 402; + optional int32 _field_name3 = 403; + optional int32 field__name4_ = 404; + optional int32 field0name5 = 405; + optional int32 field_0_name6 = 406; + optional int32 fieldName7 = 407; + optional int32 FieldName8 = 408; + optional int32 field_Name9 = 409; + optional int32 Field_Name10 = 410; + optional int32 FIELD_NAME11 = 411; + optional int32 FIELD_name12 = 412; + optional int32 __field_name13 = 413; + optional int32 __Field_name14 = 414; + optional int32 field__name15 = 415; + optional int32 field__Name16 = 416; + optional int32 field_name17__ = 417; + optional int32 Field_name18__ = 418; + + // Reserved for unknown fields test. + reserved 1000 to 9999; + + optional MessageSetCorrect message_set_correct = 500; + + // message_set test case. + message MessageSetCorrect { + option message_set_wire_format = true; + + extensions 4 to max; + } + + message MessageSetCorrectExtension1 { + extend MessageSetCorrect { + optional MessageSetCorrectExtension1 message_set_extension = 1547769; + } + optional string str = 25; + } + + message MessageSetCorrectExtension2 { + extend MessageSetCorrect { + optional MessageSetCorrectExtension2 message_set_extension = 4135312; + } + optional int32 i = 9; + } + + message ExtensionWithOneof { + oneof oneof_field { + int32 a = 1; + int32 b = 2; + } + extend MessageSetCorrect { + optional ExtensionWithOneof extension_with_oneof = 123456789; + } + } +} + +message ForeignMessageProto2 { + optional int32 c = 1; +} + +enum ForeignEnumProto2 { + FOREIGN_FOO = 0; + FOREIGN_BAR = 1; + FOREIGN_BAZ = 2; +} + +extend TestAllTypesProto2 { + optional int32 extension_int32 = 120; +} + +extend TestAllTypesProto2 { + optional group GroupField = 121 { + optional int32 group_int32 = 122; + optional uint32 group_uint32 = 123; + } +} + +message UnknownToTestAllTypes { + optional int32 optional_int32 = 1001; + optional string optional_string = 1002; + optional ForeignMessageProto2 nested_message = 1003; + optional group OptionalGroup = 1004 { + optional int32 a = 1; + } + optional bool optional_bool = 1006; + repeated int32 repeated_int32 = 1011; +} + +message NullHypothesisProto2 {} + +message EnumOnlyProto2 { + enum Bool { + kFalse = 0; + kTrue = 1; + } +} + +message OneStringProto2 { + optional string data = 1; +} + +message ProtoWithKeywords { + optional int32 inline = 1; + optional string concept = 2; + repeated string requires = 3; +} + +message TestAllRequiredTypesProto2 { + message NestedMessage { + required int32 a = 1; + required TestAllRequiredTypesProto2 corecursive = 2; + optional TestAllRequiredTypesProto2 optional_corecursive = 3; + } + + enum NestedEnum { + FOO = 0; + BAR = 1; + BAZ = 2; + NEG = -1; // Intentionally negative. + } + + // Singular + required int32 required_int32 = 1; + required int64 required_int64 = 2; + required uint32 required_uint32 = 3; + required uint64 required_uint64 = 4; + required sint32 required_sint32 = 5; + required sint64 required_sint64 = 6; + required fixed32 required_fixed32 = 7; + required fixed64 required_fixed64 = 8; + required sfixed32 required_sfixed32 = 9; + required sfixed64 required_sfixed64 = 10; + required float required_float = 11; + required double required_double = 12; + required bool required_bool = 13; + required string required_string = 14; + required bytes required_bytes = 15; + + required NestedMessage required_nested_message = 18; + required ForeignMessageProto2 required_foreign_message = 19; + + required NestedEnum required_nested_enum = 21; + required ForeignEnumProto2 required_foreign_enum = 22; + + required string required_string_piece = 24 [ctype = STRING_PIECE]; + required string required_cord = 25 [ctype = CORD]; + + required TestAllRequiredTypesProto2 recursive_message = 27; + optional TestAllRequiredTypesProto2 optional_recursive_message = 28; + + // extensions + extensions 120 to 200; + + // groups + required group Data = 201 { + required int32 group_int32 = 202; + required uint32 group_uint32 = 203; + } + + // default values + required int32 default_int32 = 241 [default = -123456789]; + required int64 default_int64 = 242 [default = -9123456789123456789]; + required uint32 default_uint32 = 243 [default = 2123456789]; + required uint64 default_uint64 = 244 [default = 10123456789123456789]; + required sint32 default_sint32 = 245 [default = -123456789]; + required sint64 default_sint64 = 246 [default = -9123456789123456789]; + required fixed32 default_fixed32 = 247 [default = 2123456789]; + required fixed64 default_fixed64 = 248 [default = 10123456789123456789]; + required sfixed32 default_sfixed32 = 249 [default = -123456789]; + required sfixed64 default_sfixed64 = 250 [default = -9123456789123456789]; + required float default_float = 251 [default = 9e9]; + required double default_double = 252 [default = 7e22]; + required bool default_bool = 253 [default = true]; + required string default_string = 254 [default = "Rosebud"]; + required bytes default_bytes = 255 [default = "joshua"]; + + // Reserved for unknown fields test. + reserved 1000 to 9999; + + // message_set test case. + message MessageSetCorrect { + option message_set_wire_format = true; + + extensions 4 to max; + } + + message MessageSetCorrectExtension1 { + extend MessageSetCorrect { + optional MessageSetCorrectExtension1 message_set_extension = 1547769; + } + required string str = 25; + } + + message MessageSetCorrectExtension2 { + extend MessageSetCorrect { + optional MessageSetCorrectExtension2 message_set_extension = 4135312; + } + required int32 i = 9; + } +} + +message TestLargeOneof { + message A1 {} + message A2 {} + message A3 {} + message A4 {} + message A5 {} + oneof large_oneof { + A1 a1 = 1; + A2 a2 = 2; + A3 a3 = 3; + A4 a4 = 4; + A5 a5 = 5; + } +} diff --git a/conformance/protos/test_messages_proto2_editions.proto b/conformance/protos/test_messages_proto2_editions.proto new file mode 100644 index 0000000..25de6c9 --- /dev/null +++ b/conformance/protos/test_messages_proto2_editions.proto @@ -0,0 +1,683 @@ +// clang-format off +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd +// +// Test schema for proto2 messages. This test schema is used by: +// +// - conformance tests + +// LINT: ALLOW_GROUPS + +edition = "2023"; + +package protobuf_test_messages.editions.proto2; + +option features.enum_type = CLOSED; +option features.repeated_field_encoding = EXPANDED; +option features.utf8_validation = NONE; +option java_package = "com.google.protobuf_test_messages.editions.proto2"; +option objc_class_prefix = "EditionsProto2"; + +// This is the default, but we specify it here explicitly. +option optimize_for = SPEED; +option cc_enable_arenas = true; + +// This proto includes every type of field in both singular and repeated +// forms. +// +// Also, crucially, all messages and enums in this file are eventually +// submessages of this message. So for example, a fuzz test of TestAllTypes +// could trigger bugs that occur in any message type in this file. We verify +// this stays true in a unit test. +message TestAllTypesProto2 { + message NestedMessage { + int32 a = 1; + TestAllTypesProto2 corecursive = 2; + } + + enum NestedEnum { + FOO = 0; + BAR = 1; + BAZ = 2; + NEG = -1; // Intentionally negative. + } + + // Singular + int32 optional_int32 = 1; + int64 optional_int64 = 2; + uint32 optional_uint32 = 3; + uint64 optional_uint64 = 4; + sint32 optional_sint32 = 5; + sint64 optional_sint64 = 6; + fixed32 optional_fixed32 = 7; + fixed64 optional_fixed64 = 8; + sfixed32 optional_sfixed32 = 9; + sfixed64 optional_sfixed64 = 10; + float optional_float = 11; + double optional_double = 12; + bool optional_bool = 13; + string optional_string = 14; + bytes optional_bytes = 15; + NestedMessage optional_nested_message = 18; + ForeignMessageProto2 optional_foreign_message = 19; + NestedEnum optional_nested_enum = 21; + ForeignEnumProto2 optional_foreign_enum = 22; + string optional_string_piece = 24 [ + ctype = STRING_PIECE + ]; + + string optional_cord = 25 [ + ctype = CORD + ]; + + TestAllTypesProto2 recursive_message = 27; + + // Repeated + repeated int32 repeated_int32 = 31; + repeated int64 repeated_int64 = 32; + repeated uint32 repeated_uint32 = 33; + repeated uint64 repeated_uint64 = 34; + repeated sint32 repeated_sint32 = 35; + repeated sint64 repeated_sint64 = 36; + repeated fixed32 repeated_fixed32 = 37; + repeated fixed64 repeated_fixed64 = 38; + repeated sfixed32 repeated_sfixed32 = 39; + repeated sfixed64 repeated_sfixed64 = 40; + repeated float repeated_float = 41; + repeated double repeated_double = 42; + repeated bool repeated_bool = 43; + repeated string repeated_string = 44; + repeated bytes repeated_bytes = 45; + repeated NestedMessage repeated_nested_message = 48; + repeated ForeignMessageProto2 repeated_foreign_message = 49; + repeated NestedEnum repeated_nested_enum = 51; + repeated ForeignEnumProto2 repeated_foreign_enum = 52; + repeated string repeated_string_piece = 54 [ + ctype = STRING_PIECE + ]; + + repeated string repeated_cord = 55 [ + ctype = CORD + ]; + + // Packed + repeated int32 packed_int32 = 75 [ + features.repeated_field_encoding = PACKED + ]; + + repeated int64 packed_int64 = 76 [ + features.repeated_field_encoding = PACKED + ]; + + repeated uint32 packed_uint32 = 77 [ + features.repeated_field_encoding = PACKED + ]; + + repeated uint64 packed_uint64 = 78 [ + features.repeated_field_encoding = PACKED + ]; + + repeated sint32 packed_sint32 = 79 [ + features.repeated_field_encoding = PACKED + ]; + + repeated sint64 packed_sint64 = 80 [ + features.repeated_field_encoding = PACKED + ]; + + repeated fixed32 packed_fixed32 = 81 [ + features.repeated_field_encoding = PACKED + ]; + + repeated fixed64 packed_fixed64 = 82 [ + features.repeated_field_encoding = PACKED + ]; + + repeated sfixed32 packed_sfixed32 = 83 [ + features.repeated_field_encoding = PACKED + ]; + + repeated sfixed64 packed_sfixed64 = 84 [ + features.repeated_field_encoding = PACKED + ]; + + repeated float packed_float = 85 [ + features.repeated_field_encoding = PACKED + ]; + + repeated double packed_double = 86 [ + features.repeated_field_encoding = PACKED + ]; + + repeated bool packed_bool = 87 [ + features.repeated_field_encoding = PACKED + ]; + + repeated NestedEnum packed_nested_enum = 88 [ + features.repeated_field_encoding = PACKED + ]; + + // Unpacked + repeated int32 unpacked_int32 = 89; + repeated int64 unpacked_int64 = 90; + repeated uint32 unpacked_uint32 = 91; + repeated uint64 unpacked_uint64 = 92; + repeated sint32 unpacked_sint32 = 93; + repeated sint64 unpacked_sint64 = 94; + repeated fixed32 unpacked_fixed32 = 95; + repeated fixed64 unpacked_fixed64 = 96; + repeated sfixed32 unpacked_sfixed32 = 97; + repeated sfixed64 unpacked_sfixed64 = 98; + repeated float unpacked_float = 99; + repeated double unpacked_double = 100; + repeated bool unpacked_bool = 101; + repeated NestedEnum unpacked_nested_enum = 102; + + // Map + map map_int32_int32 = 56; + map map_int64_int64 = 57; + map map_uint32_uint32 = 58; + map map_uint64_uint64 = 59; + map map_sint32_sint32 = 60; + map map_sint64_sint64 = 61; + map map_fixed32_fixed32 = 62; + map map_fixed64_fixed64 = 63; + map map_sfixed32_sfixed32 = 64; + map map_sfixed64_sfixed64 = 65; + map map_int32_bool = 104; + map map_int32_float = 66; + map map_int32_double = 67; + map map_int32_nested_message = 103; + map map_bool_bool = 68; + map map_string_string = 69; + map map_string_bytes = 70; + map map_string_nested_message = 71; + map map_string_foreign_message = 72; + map map_string_nested_enum = 73; + map map_string_foreign_enum = 74; + + oneof oneof_field { + uint32 oneof_uint32 = 111; + NestedMessage oneof_nested_message = 112; + string oneof_string = 113; + bytes oneof_bytes = 114; + bool oneof_bool = 115; + uint64 oneof_uint64 = 116; + float oneof_float = 117; + double oneof_double = 118; + NestedEnum oneof_enum = 119; + } + + // extensions + extensions 120 to 200; + + // groups + message Data { + int32 group_int32 = 202; + uint32 group_uint32 = 203; + } + + Data data = 201 [ + features.message_encoding = DELIMITED + ]; + + message MultiWordGroupField { + int32 group_int32 = 205; + uint32 group_uint32 = 206; + } + + MultiWordGroupField multiwordgroupfield = 204 [ + features.message_encoding = DELIMITED + ]; + + // default values + int32 default_int32 = 241 [ + default = -123456789 + ]; + + int64 default_int64 = 242 [ + default = -9123456789123456789 + ]; + + uint32 default_uint32 = 243 [ + default = 2123456789 + ]; + + uint64 default_uint64 = 244 [ + default = 10123456789123456789 + ]; + + sint32 default_sint32 = 245 [ + default = -123456789 + ]; + + sint64 default_sint64 = 246 [ + default = -9123456789123456789 + ]; + + fixed32 default_fixed32 = 247 [ + default = 2123456789 + ]; + + fixed64 default_fixed64 = 248 [ + default = 10123456789123456789 + ]; + + sfixed32 default_sfixed32 = 249 [ + default = -123456789 + ]; + + sfixed64 default_sfixed64 = 250 [ + default = -9123456789123456789 + ]; + + float default_float = 251 [ + default = 9e9 + ]; + + double default_double = 252 [ + default = 7e22 + ]; + + bool default_bool = 253 [ + default = true + ]; + + string default_string = 254 [ + default = "Rosebud" + ]; + + bytes default_bytes = 255 [ + default = "joshua" + ]; + + // Test field-name-to-JSON-name convention. + // (protobuf says names can be any valid C/C++ identifier.) + int32 fieldname1 = 401; + int32 field_name2 = 402; + int32 _field_name3 = 403; + int32 field__name4_ = 404; + int32 field0name5 = 405; + int32 field_0_name6 = 406; + int32 fieldName7 = 407; + int32 FieldName8 = 408; + int32 field_Name9 = 409; + int32 Field_Name10 = 410; + int32 FIELD_NAME11 = 411; + int32 FIELD_name12 = 412; + int32 __field_name13 = 413; + int32 __Field_name14 = 414; + int32 field__name15 = 415; + int32 field__Name16 = 416; + int32 field_name17__ = 417; + int32 Field_name18__ = 418; + + // Reserved for unknown fields test. + reserved 1000 to 9999; + + MessageSetCorrect message_set_correct = 500; + + // message_set test case. + message MessageSetCorrect { + option message_set_wire_format = true; + + extensions 4 to max; + } + + message MessageSetCorrectExtension1 { + extend MessageSetCorrect { + MessageSetCorrectExtension1 message_set_extension = 1547769; + } + + string str = 25; + } + + message MessageSetCorrectExtension2 { + extend MessageSetCorrect { + MessageSetCorrectExtension2 message_set_extension = 4135312; + } + + int32 i = 9; + } + + message ExtensionWithOneof { + oneof oneof_field { + int32 a = 1; + int32 b = 2; + } + + extend MessageSetCorrect { + ExtensionWithOneof extension_with_oneof = 123456789; + } + } +} + +message ForeignMessageProto2 { + int32 c = 1; +} + +enum ForeignEnumProto2 { + FOREIGN_FOO = 0; + FOREIGN_BAR = 1; + FOREIGN_BAZ = 2; +} + +extend TestAllTypesProto2 { + int32 extension_int32 = 120; +} + +extend TestAllTypesProto2 { + GroupField groupfield = 121 [ + features.message_encoding = DELIMITED + ]; +} + +message GroupField { + int32 group_int32 = 122; + uint32 group_uint32 = 123; +} + +message UnknownToTestAllTypes { + int32 optional_int32 = 1001; + string optional_string = 1002; + ForeignMessageProto2 nested_message = 1003; + + message OptionalGroup { + int32 a = 1; + } + + OptionalGroup optionalgroup = 1004 [ + features.message_encoding = DELIMITED + ]; + + bool optional_bool = 1006; + repeated int32 repeated_int32 = 1011; +} + +message NullHypothesisProto2 { +} + +message EnumOnlyProto2 { + enum Bool { + kFalse = 0; + kTrue = 1; + } +} + +message OneStringProto2 { + string data = 1; +} + +message ProtoWithKeywords { + int32 inline = 1; + string concept = 2; + repeated string requires = 3; +} + +message TestAllRequiredTypesProto2 { + message NestedMessage { + int32 a = 1 [ + features.field_presence = LEGACY_REQUIRED + ]; + + TestAllRequiredTypesProto2 corecursive = 2 [ + features.field_presence = LEGACY_REQUIRED + ]; + + TestAllRequiredTypesProto2 optional_corecursive = 3; + } + + enum NestedEnum { + FOO = 0; + BAR = 1; + BAZ = 2; + NEG = -1; // Intentionally negative. + } + + // Singular + int32 required_int32 = 1 [ + features.field_presence = LEGACY_REQUIRED + ]; + + int64 required_int64 = 2 [ + features.field_presence = LEGACY_REQUIRED + ]; + + uint32 required_uint32 = 3 [ + features.field_presence = LEGACY_REQUIRED + ]; + + uint64 required_uint64 = 4 [ + features.field_presence = LEGACY_REQUIRED + ]; + + sint32 required_sint32 = 5 [ + features.field_presence = LEGACY_REQUIRED + ]; + + sint64 required_sint64 = 6 [ + features.field_presence = LEGACY_REQUIRED + ]; + + fixed32 required_fixed32 = 7 [ + features.field_presence = LEGACY_REQUIRED + ]; + + fixed64 required_fixed64 = 8 [ + features.field_presence = LEGACY_REQUIRED + ]; + + sfixed32 required_sfixed32 = 9 [ + features.field_presence = LEGACY_REQUIRED + ]; + + sfixed64 required_sfixed64 = 10 [ + features.field_presence = LEGACY_REQUIRED + ]; + + float required_float = 11 [ + features.field_presence = LEGACY_REQUIRED + ]; + + double required_double = 12 [ + features.field_presence = LEGACY_REQUIRED + ]; + + bool required_bool = 13 [ + features.field_presence = LEGACY_REQUIRED + ]; + + string required_string = 14 [ + features.field_presence = LEGACY_REQUIRED + ]; + + bytes required_bytes = 15 [ + features.field_presence = LEGACY_REQUIRED + ]; + + NestedMessage required_nested_message = 18 [ + features.field_presence = LEGACY_REQUIRED + ]; + + ForeignMessageProto2 required_foreign_message = 19 [ + features.field_presence = LEGACY_REQUIRED + ]; + + NestedEnum required_nested_enum = 21 [ + features.field_presence = LEGACY_REQUIRED + ]; + + ForeignEnumProto2 required_foreign_enum = 22 [ + features.field_presence = LEGACY_REQUIRED + ]; + + string required_string_piece = 24 [ + features.field_presence = LEGACY_REQUIRED, + ctype = STRING_PIECE + ]; + + string required_cord = 25 [ + features.field_presence = LEGACY_REQUIRED, + ctype = CORD + ]; + + TestAllRequiredTypesProto2 recursive_message = 27 [ + features.field_presence = LEGACY_REQUIRED + ]; + + TestAllRequiredTypesProto2 optional_recursive_message = 28; + + // extensions + extensions 120 to 200; + + // groups + message Data { + int32 group_int32 = 202 [ + features.field_presence = LEGACY_REQUIRED + ]; + + uint32 group_uint32 = 203 [ + features.field_presence = LEGACY_REQUIRED + ]; + } + + Data data = 201 [ + features.field_presence = LEGACY_REQUIRED, + features.message_encoding = DELIMITED + ]; + + // default values + int32 default_int32 = 241 [ + features.field_presence = LEGACY_REQUIRED, + default = -123456789 + ]; + + int64 default_int64 = 242 [ + features.field_presence = LEGACY_REQUIRED, + default = -9123456789123456789 + ]; + + uint32 default_uint32 = 243 [ + features.field_presence = LEGACY_REQUIRED, + default = 2123456789 + ]; + + uint64 default_uint64 = 244 [ + features.field_presence = LEGACY_REQUIRED, + default = 10123456789123456789 + ]; + + sint32 default_sint32 = 245 [ + features.field_presence = LEGACY_REQUIRED, + default = -123456789 + ]; + + sint64 default_sint64 = 246 [ + features.field_presence = LEGACY_REQUIRED, + default = -9123456789123456789 + ]; + + fixed32 default_fixed32 = 247 [ + features.field_presence = LEGACY_REQUIRED, + default = 2123456789 + ]; + + fixed64 default_fixed64 = 248 [ + features.field_presence = LEGACY_REQUIRED, + default = 10123456789123456789 + ]; + + sfixed32 default_sfixed32 = 249 [ + features.field_presence = LEGACY_REQUIRED, + default = -123456789 + ]; + + sfixed64 default_sfixed64 = 250 [ + features.field_presence = LEGACY_REQUIRED, + default = -9123456789123456789 + ]; + + float default_float = 251 [ + features.field_presence = LEGACY_REQUIRED, + default = 9e9 + ]; + + double default_double = 252 [ + features.field_presence = LEGACY_REQUIRED, + default = 7e22 + ]; + + bool default_bool = 253 [ + features.field_presence = LEGACY_REQUIRED, + default = true + ]; + + string default_string = 254 [ + features.field_presence = LEGACY_REQUIRED, + default = "Rosebud" + ]; + + bytes default_bytes = 255 [ + features.field_presence = LEGACY_REQUIRED, + default = "joshua" + ]; + + // Reserved for unknown fields test. + reserved 1000 to 9999; + + // message_set test case. + message MessageSetCorrect { + option message_set_wire_format = true; + + extensions 4 to max; + } + + message MessageSetCorrectExtension1 { + extend MessageSetCorrect { + MessageSetCorrectExtension1 message_set_extension = 1547769; + } + + string str = 25 [ + features.field_presence = LEGACY_REQUIRED + ]; + } + + message MessageSetCorrectExtension2 { + extend MessageSetCorrect { + MessageSetCorrectExtension2 message_set_extension = 4135312; + } + + int32 i = 9 [ + features.field_presence = LEGACY_REQUIRED + ]; + } +} + +message TestLargeOneof { + message A1 { + } + + message A2 { + } + + message A3 { + } + + message A4 { + } + + message A5 { + } + + oneof large_oneof { + A1 a1 = 1; + A2 a2 = 2; + A3 a3 = 3; + A4 a4 = 4; + A5 a5 = 5; + } +} diff --git a/conformance/protos/test_messages_proto3.proto b/conformance/protos/test_messages_proto3.proto new file mode 100644 index 0000000..b78e631 --- /dev/null +++ b/conformance/protos/test_messages_proto3.proto @@ -0,0 +1,266 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd +// +// Test schema for proto3 messages. This test schema is used by: +// +// - benchmarks +// - fuzz tests +// - conformance tests +// + +syntax = "proto3"; + +package protobuf_test_messages.proto3; + +option java_package = "com.google.protobuf_test_messages.proto3"; +option objc_class_prefix = "Proto3"; + +// This is the default, but we specify it here explicitly. +option optimize_for = SPEED; + +import "google/protobuf/any.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/field_mask.proto"; +import "google/protobuf/struct.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; + +option cc_enable_arenas = true; + +// This proto includes every type of field in both singular and repeated +// forms. +// +// Also, crucially, all messages and enums in this file are eventually +// submessages of this message. So for example, a fuzz test of TestAllTypes +// could trigger bugs that occur in any message type in this file. We verify +// this stays true in a unit test. +message TestAllTypesProto3 { + message NestedMessage { + int32 a = 1; + TestAllTypesProto3 corecursive = 2; + } + + enum NestedEnum { + FOO = 0; + BAR = 1; + BAZ = 2; + NEG = -1; // Intentionally negative. + } + + enum AliasedEnum { + option allow_alias = true; + + ALIAS_FOO = 0; + ALIAS_BAR = 1; + ALIAS_BAZ = 2; + MOO = 2; + moo = 2; + bAz = 2; + } + + // Singular + // test [kotlin] comment + int32 optional_int32 = 1; + int64 optional_int64 = 2; + uint32 optional_uint32 = 3; + uint64 optional_uint64 = 4; + sint32 optional_sint32 = 5; + sint64 optional_sint64 = 6; + fixed32 optional_fixed32 = 7; + fixed64 optional_fixed64 = 8; + sfixed32 optional_sfixed32 = 9; + sfixed64 optional_sfixed64 = 10; + float optional_float = 11; + double optional_double = 12; + bool optional_bool = 13; + string optional_string = 14; + bytes optional_bytes = 15; + + NestedMessage optional_nested_message = 18; + ForeignMessage optional_foreign_message = 19; + + NestedEnum optional_nested_enum = 21; + ForeignEnum optional_foreign_enum = 22; + AliasedEnum optional_aliased_enum = 23; + + string optional_string_piece = 24 [ctype = STRING_PIECE]; + string optional_cord = 25 [ctype = CORD]; + + TestAllTypesProto3 recursive_message = 27; + + // Repeated + repeated int32 repeated_int32 = 31; + repeated int64 repeated_int64 = 32; + repeated uint32 repeated_uint32 = 33; + repeated uint64 repeated_uint64 = 34; + repeated sint32 repeated_sint32 = 35; + repeated sint64 repeated_sint64 = 36; + repeated fixed32 repeated_fixed32 = 37; + repeated fixed64 repeated_fixed64 = 38; + repeated sfixed32 repeated_sfixed32 = 39; + repeated sfixed64 repeated_sfixed64 = 40; + repeated float repeated_float = 41; + repeated double repeated_double = 42; + repeated bool repeated_bool = 43; + repeated string repeated_string = 44; + repeated bytes repeated_bytes = 45; + + repeated NestedMessage repeated_nested_message = 48; + repeated ForeignMessage repeated_foreign_message = 49; + + repeated NestedEnum repeated_nested_enum = 51; + repeated ForeignEnum repeated_foreign_enum = 52; + + repeated string repeated_string_piece = 54 [ctype = STRING_PIECE]; + repeated string repeated_cord = 55 [ctype = CORD]; + + // Packed + repeated int32 packed_int32 = 75 [packed = true]; + repeated int64 packed_int64 = 76 [packed = true]; + repeated uint32 packed_uint32 = 77 [packed = true]; + repeated uint64 packed_uint64 = 78 [packed = true]; + repeated sint32 packed_sint32 = 79 [packed = true]; + repeated sint64 packed_sint64 = 80 [packed = true]; + repeated fixed32 packed_fixed32 = 81 [packed = true]; + repeated fixed64 packed_fixed64 = 82 [packed = true]; + repeated sfixed32 packed_sfixed32 = 83 [packed = true]; + repeated sfixed64 packed_sfixed64 = 84 [packed = true]; + repeated float packed_float = 85 [packed = true]; + repeated double packed_double = 86 [packed = true]; + repeated bool packed_bool = 87 [packed = true]; + repeated NestedEnum packed_nested_enum = 88 [packed = true]; + + // Unpacked + repeated int32 unpacked_int32 = 89 [packed = false]; + repeated int64 unpacked_int64 = 90 [packed = false]; + repeated uint32 unpacked_uint32 = 91 [packed = false]; + repeated uint64 unpacked_uint64 = 92 [packed = false]; + repeated sint32 unpacked_sint32 = 93 [packed = false]; + repeated sint64 unpacked_sint64 = 94 [packed = false]; + repeated fixed32 unpacked_fixed32 = 95 [packed = false]; + repeated fixed64 unpacked_fixed64 = 96 [packed = false]; + repeated sfixed32 unpacked_sfixed32 = 97 [packed = false]; + repeated sfixed64 unpacked_sfixed64 = 98 [packed = false]; + repeated float unpacked_float = 99 [packed = false]; + repeated double unpacked_double = 100 [packed = false]; + repeated bool unpacked_bool = 101 [packed = false]; + repeated NestedEnum unpacked_nested_enum = 102 [packed = false]; + + // Map + map map_int32_int32 = 56; + map map_int64_int64 = 57; + map map_uint32_uint32 = 58; + map map_uint64_uint64 = 59; + map map_sint32_sint32 = 60; + map map_sint64_sint64 = 61; + map map_fixed32_fixed32 = 62; + map map_fixed64_fixed64 = 63; + map map_sfixed32_sfixed32 = 64; + map map_sfixed64_sfixed64 = 65; + map map_int32_float = 66; + map map_int32_double = 67; + map map_bool_bool = 68; + map map_string_string = 69; + map map_string_bytes = 70; + map map_string_nested_message = 71; + map map_string_foreign_message = 72; + map map_string_nested_enum = 73; + map map_string_foreign_enum = 74; + + oneof oneof_field { + uint32 oneof_uint32 = 111; + NestedMessage oneof_nested_message = 112; + string oneof_string = 113; + bytes oneof_bytes = 114; + bool oneof_bool = 115; + uint64 oneof_uint64 = 116; + float oneof_float = 117; + double oneof_double = 118; + NestedEnum oneof_enum = 119; + google.protobuf.NullValue oneof_null_value = 120; + } + + // Well-known types + google.protobuf.BoolValue optional_bool_wrapper = 201; + google.protobuf.Int32Value optional_int32_wrapper = 202; + google.protobuf.Int64Value optional_int64_wrapper = 203; + google.protobuf.UInt32Value optional_uint32_wrapper = 204; + google.protobuf.UInt64Value optional_uint64_wrapper = 205; + google.protobuf.FloatValue optional_float_wrapper = 206; + google.protobuf.DoubleValue optional_double_wrapper = 207; + google.protobuf.StringValue optional_string_wrapper = 208; + google.protobuf.BytesValue optional_bytes_wrapper = 209; + + repeated google.protobuf.BoolValue repeated_bool_wrapper = 211; + repeated google.protobuf.Int32Value repeated_int32_wrapper = 212; + repeated google.protobuf.Int64Value repeated_int64_wrapper = 213; + repeated google.protobuf.UInt32Value repeated_uint32_wrapper = 214; + repeated google.protobuf.UInt64Value repeated_uint64_wrapper = 215; + repeated google.protobuf.FloatValue repeated_float_wrapper = 216; + repeated google.protobuf.DoubleValue repeated_double_wrapper = 217; + repeated google.protobuf.StringValue repeated_string_wrapper = 218; + repeated google.protobuf.BytesValue repeated_bytes_wrapper = 219; + + google.protobuf.Duration optional_duration = 301; + google.protobuf.Timestamp optional_timestamp = 302; + google.protobuf.FieldMask optional_field_mask = 303; + google.protobuf.Struct optional_struct = 304; + google.protobuf.Any optional_any = 305; + google.protobuf.Value optional_value = 306; + google.protobuf.NullValue optional_null_value = 307; + + repeated google.protobuf.Duration repeated_duration = 311; + repeated google.protobuf.Timestamp repeated_timestamp = 312; + repeated google.protobuf.FieldMask repeated_fieldmask = 313; + repeated google.protobuf.Struct repeated_struct = 324; + repeated google.protobuf.Any repeated_any = 315; + repeated google.protobuf.Value repeated_value = 316; + repeated google.protobuf.ListValue repeated_list_value = 317; + + // Test field-name-to-JSON-name convention. + // (protobuf says names can be any valid C/C++ identifier.) + int32 fieldname1 = 401; + int32 field_name2 = 402; + int32 _field_name3 = 403; + int32 field__name4_ = 404; + int32 field0name5 = 405; + int32 field_0_name6 = 406; + int32 fieldName7 = 407; + int32 FieldName8 = 408; + int32 field_Name9 = 409; + int32 Field_Name10 = 410; + int32 FIELD_NAME11 = 411; + int32 FIELD_name12 = 412; + int32 __field_name13 = 413; + int32 __Field_name14 = 414; + int32 field__name15 = 415; + int32 field__Name16 = 416; + int32 field_name17__ = 417; + int32 Field_name18__ = 418; + + // Reserved for testing unknown fields + reserved 501 to 510; +} + +message ForeignMessage { + int32 c = 1; +} + +enum ForeignEnum { + FOREIGN_FOO = 0; + FOREIGN_BAR = 1; + FOREIGN_BAZ = 2; +} + +message NullHypothesisProto3 {} + +message EnumOnlyProto3 { + enum Bool { + kFalse = 0; + kTrue = 1; + } +} diff --git a/conformance/protos/test_messages_proto3_editions.proto b/conformance/protos/test_messages_proto3_editions.proto new file mode 100644 index 0000000..dc2cd21 --- /dev/null +++ b/conformance/protos/test_messages_proto3_editions.proto @@ -0,0 +1,309 @@ +// clang-format off +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd +// +// Test schema for proto3 messages. This test schema is used by: +// +// - benchmarks +// - fuzz tests +// - conformance tests + +edition = "2023"; + +package protobuf_test_messages.editions.proto3; + +import "google/protobuf/any.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/field_mask.proto"; +import "google/protobuf/struct.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; + +option features.field_presence = IMPLICIT; +option java_package = "com.google.protobuf_test_messages.editions.proto3"; +option objc_class_prefix = "EditionsProto3"; + +// This is the default, but we specify it here explicitly. +option optimize_for = SPEED; +option cc_enable_arenas = true; + +// This proto includes every type of field in both singular and repeated +// forms. +// +// Also, crucially, all messages and enums in this file are eventually +// submessages of this message. So for example, a fuzz test of TestAllTypes +// could trigger bugs that occur in any message type in this file. We verify +// this stays true in a unit test. +message TestAllTypesProto3 { + message NestedMessage { + int32 a = 1; + TestAllTypesProto3 corecursive = 2; + } + + enum NestedEnum { + FOO = 0; + BAR = 1; + BAZ = 2; + NEG = -1; // Intentionally negative. + } + + enum AliasedEnum { + option allow_alias = true; + + ALIAS_FOO = 0; + ALIAS_BAR = 1; + ALIAS_BAZ = 2; + MOO = 2; + moo = 2; + bAz = 2; + } + + // Singular + // test [kotlin] comment + int32 optional_int32 = 1; + int64 optional_int64 = 2; + uint32 optional_uint32 = 3; + uint64 optional_uint64 = 4; + sint32 optional_sint32 = 5; + sint64 optional_sint64 = 6; + fixed32 optional_fixed32 = 7; + fixed64 optional_fixed64 = 8; + sfixed32 optional_sfixed32 = 9; + sfixed64 optional_sfixed64 = 10; + float optional_float = 11; + double optional_double = 12; + bool optional_bool = 13; + string optional_string = 14; + bytes optional_bytes = 15; + NestedMessage optional_nested_message = 18; + ForeignMessage optional_foreign_message = 19; + NestedEnum optional_nested_enum = 21; + ForeignEnum optional_foreign_enum = 22; + AliasedEnum optional_aliased_enum = 23; + string optional_string_piece = 24 [ + ctype = STRING_PIECE + ]; + + string optional_cord = 25 [ + ctype = CORD + ]; + + TestAllTypesProto3 recursive_message = 27; + + // Repeated + repeated int32 repeated_int32 = 31; + repeated int64 repeated_int64 = 32; + repeated uint32 repeated_uint32 = 33; + repeated uint64 repeated_uint64 = 34; + repeated sint32 repeated_sint32 = 35; + repeated sint64 repeated_sint64 = 36; + repeated fixed32 repeated_fixed32 = 37; + repeated fixed64 repeated_fixed64 = 38; + repeated sfixed32 repeated_sfixed32 = 39; + repeated sfixed64 repeated_sfixed64 = 40; + repeated float repeated_float = 41; + repeated double repeated_double = 42; + repeated bool repeated_bool = 43; + repeated string repeated_string = 44; + repeated bytes repeated_bytes = 45; + repeated NestedMessage repeated_nested_message = 48; + repeated ForeignMessage repeated_foreign_message = 49; + repeated NestedEnum repeated_nested_enum = 51; + repeated ForeignEnum repeated_foreign_enum = 52; + repeated string repeated_string_piece = 54 [ + ctype = STRING_PIECE + ]; + + repeated string repeated_cord = 55 [ + ctype = CORD + ]; + + // Packed + repeated int32 packed_int32 = 75; + repeated int64 packed_int64 = 76; + repeated uint32 packed_uint32 = 77; + repeated uint64 packed_uint64 = 78; + repeated sint32 packed_sint32 = 79; + repeated sint64 packed_sint64 = 80; + repeated fixed32 packed_fixed32 = 81; + repeated fixed64 packed_fixed64 = 82; + repeated sfixed32 packed_sfixed32 = 83; + repeated sfixed64 packed_sfixed64 = 84; + repeated float packed_float = 85; + repeated double packed_double = 86; + repeated bool packed_bool = 87; + repeated NestedEnum packed_nested_enum = 88; + + // Unpacked + repeated int32 unpacked_int32 = 89 [ + features.repeated_field_encoding = EXPANDED + ]; + + repeated int64 unpacked_int64 = 90 [ + features.repeated_field_encoding = EXPANDED + ]; + + repeated uint32 unpacked_uint32 = 91 [ + features.repeated_field_encoding = EXPANDED + ]; + + repeated uint64 unpacked_uint64 = 92 [ + features.repeated_field_encoding = EXPANDED + ]; + + repeated sint32 unpacked_sint32 = 93 [ + features.repeated_field_encoding = EXPANDED + ]; + + repeated sint64 unpacked_sint64 = 94 [ + features.repeated_field_encoding = EXPANDED + ]; + + repeated fixed32 unpacked_fixed32 = 95 [ + features.repeated_field_encoding = EXPANDED + ]; + + repeated fixed64 unpacked_fixed64 = 96 [ + features.repeated_field_encoding = EXPANDED + ]; + + repeated sfixed32 unpacked_sfixed32 = 97 [ + features.repeated_field_encoding = EXPANDED + ]; + + repeated sfixed64 unpacked_sfixed64 = 98 [ + features.repeated_field_encoding = EXPANDED + ]; + + repeated float unpacked_float = 99 [ + features.repeated_field_encoding = EXPANDED + ]; + + repeated double unpacked_double = 100 [ + features.repeated_field_encoding = EXPANDED + ]; + + repeated bool unpacked_bool = 101 [ + features.repeated_field_encoding = EXPANDED + ]; + + repeated NestedEnum unpacked_nested_enum = 102 [ + features.repeated_field_encoding = EXPANDED + ]; + + // Map + map map_int32_int32 = 56; + map map_int64_int64 = 57; + map map_uint32_uint32 = 58; + map map_uint64_uint64 = 59; + map map_sint32_sint32 = 60; + map map_sint64_sint64 = 61; + map map_fixed32_fixed32 = 62; + map map_fixed64_fixed64 = 63; + map map_sfixed32_sfixed32 = 64; + map map_sfixed64_sfixed64 = 65; + map map_int32_float = 66; + map map_int32_double = 67; + map map_bool_bool = 68; + map map_string_string = 69; + map map_string_bytes = 70; + map map_string_nested_message = 71; + map map_string_foreign_message = 72; + map map_string_nested_enum = 73; + map map_string_foreign_enum = 74; + + oneof oneof_field { + uint32 oneof_uint32 = 111; + NestedMessage oneof_nested_message = 112; + string oneof_string = 113; + bytes oneof_bytes = 114; + bool oneof_bool = 115; + uint64 oneof_uint64 = 116; + float oneof_float = 117; + double oneof_double = 118; + NestedEnum oneof_enum = 119; + google.protobuf.NullValue oneof_null_value = 120; + } + + // Well-known types + google.protobuf.BoolValue optional_bool_wrapper = 201; + google.protobuf.Int32Value optional_int32_wrapper = 202; + google.protobuf.Int64Value optional_int64_wrapper = 203; + google.protobuf.UInt32Value optional_uint32_wrapper = 204; + google.protobuf.UInt64Value optional_uint64_wrapper = 205; + google.protobuf.FloatValue optional_float_wrapper = 206; + google.protobuf.DoubleValue optional_double_wrapper = 207; + google.protobuf.StringValue optional_string_wrapper = 208; + google.protobuf.BytesValue optional_bytes_wrapper = 209; + repeated google.protobuf.BoolValue repeated_bool_wrapper = 211; + repeated google.protobuf.Int32Value repeated_int32_wrapper = 212; + repeated google.protobuf.Int64Value repeated_int64_wrapper = 213; + repeated google.protobuf.UInt32Value repeated_uint32_wrapper = 214; + repeated google.protobuf.UInt64Value repeated_uint64_wrapper = 215; + repeated google.protobuf.FloatValue repeated_float_wrapper = 216; + repeated google.protobuf.DoubleValue repeated_double_wrapper = 217; + repeated google.protobuf.StringValue repeated_string_wrapper = 218; + repeated google.protobuf.BytesValue repeated_bytes_wrapper = 219; + google.protobuf.Duration optional_duration = 301; + google.protobuf.Timestamp optional_timestamp = 302; + google.protobuf.FieldMask optional_field_mask = 303; + google.protobuf.Struct optional_struct = 304; + google.protobuf.Any optional_any = 305; + google.protobuf.Value optional_value = 306; + google.protobuf.NullValue optional_null_value = 307; + repeated google.protobuf.Duration repeated_duration = 311; + repeated google.protobuf.Timestamp repeated_timestamp = 312; + repeated google.protobuf.FieldMask repeated_fieldmask = 313; + repeated google.protobuf.Struct repeated_struct = 324; + repeated google.protobuf.Any repeated_any = 315; + repeated google.protobuf.Value repeated_value = 316; + repeated google.protobuf.ListValue repeated_list_value = 317; + + // Test field-name-to-JSON-name convention. + // (protobuf says names can be any valid C/C++ identifier.) + int32 fieldname1 = 401; + int32 field_name2 = 402; + int32 _field_name3 = 403; + int32 field__name4_ = 404; + int32 field0name5 = 405; + int32 field_0_name6 = 406; + int32 fieldName7 = 407; + int32 FieldName8 = 408; + int32 field_Name9 = 409; + int32 Field_Name10 = 410; + int32 FIELD_NAME11 = 411; + int32 FIELD_name12 = 412; + int32 __field_name13 = 413; + int32 __Field_name14 = 414; + int32 field__name15 = 415; + int32 field__Name16 = 416; + int32 field_name17__ = 417; + int32 Field_name18__ = 418; + + // Reserved for testing unknown fields + reserved 501 to 510; +} + +message ForeignMessage { + int32 c = 1; +} + +enum ForeignEnum { + FOREIGN_FOO = 0; + FOREIGN_BAR = 1; + FOREIGN_BAZ = 2; +} + +message NullHypothesisProto3 { +} + +message EnumOnlyProto3 { + enum Bool { + kFalse = 0; + kTrue = 1; + } +} diff --git a/conformance/runner.js b/conformance/runner.js new file mode 100755 index 0000000..77161c3 --- /dev/null +++ b/conformance/runner.js @@ -0,0 +1,175 @@ +#!/usr/bin/env -S node + +const { ConformanceRequest, ConformanceResponse, WireFormat } = require('./protos/conformance_pb.js'); +const { TestAllTypesProto2 } = require('./protos/test_messages_proto2_pb.js'); +const { TestAllTypesProto3 } = require('./protos/test_messages_proto3_pb.js'); +const { TestAllTypesEdition2023 } = require('./protos/test_messages_edition2023_pb.js'); +const { TestAllTypesProto2: TestAllTypesProto2Edition2023 } = require('./protos/test_messages_proto2_editions_pb.js'); +const { TestAllTypesProto3: TestAllTypesProto3Edition2023 } = require('./protos/test_messages_proto3_editions_pb.js'); + +const fs = require('fs'); + +/** + * Creates a `proto.conformance.ConformanceResponse` response according to the + * `proto.conformance.ConformanceRequest` request. + * @param {!ConformanceRequest} request + * @return {!ConformanceResponse} response + */ +function doTest(request) { + const response = new ConformanceResponse(); + + if (request.getPayloadCase() === ConformanceRequest.PayloadCase.JSON_PAYLOAD) { + response.setSkipped('Json is not supported as input format.'); + return response; + } + + if (request.getPayloadCase() === ConformanceRequest.PayloadCase.TEXT_PAYLOAD) { + response.setSkipped('Text format is not supported as input format.'); + return response; + } + + if (request.getPayloadCase() === ConformanceRequest.PayloadCase.PAYLOAD_NOT_SET) { + response.setRuntimeError('Request didn\'t have payload.'); + return response; + } + + if (request.getPayloadCase() !== ConformanceRequest.PayloadCase.PROTOBUF_PAYLOAD) { + throw new Error('Request didn\'t have accepted input format.'); + } + + if (request.getRequestedOutputFormat() === WireFormat.JSON) { + response.setSkipped('Json is not supported as output format.'); + return response; + } + + if (request.getRequestedOutputFormat() === WireFormat.TEXT_FORMAT) { + response.setSkipped('Text format is not supported as output format.'); + return response; + } + + if (request.getRequestedOutputFormat() === WireFormat.TEXT_FORMAT) { + response.setRuntimeError('Unspecified output format'); + return response; + } + + if (request.getRequestedOutputFormat() !== WireFormat.PROTOBUF) { + throw new Error('Request didn\'t have accepted output format.'); + } + + if (request.getMessageType() === 'conformance.FailureSet') { + response.setProtobufPayload(new ArrayBuffer(0)); + } else if ( + request.getMessageType() === + 'protobuf_test_messages.proto2.TestAllTypesProto2') { + try { + const testMessage = + TestAllTypesProto2.deserializeBinary(request.getProtobufPayload()); + response.setProtobufPayload(testMessage.serializeBinary()); + } catch (err) { + response.setParseError(err.toString()); + } + } else if ( + request.getMessageType() === + 'protobuf_test_messages.proto3.TestAllTypesProto3') { + try { + const testMessage = + TestAllTypesProto3.deserializeBinary(request.getProtobufPayload()); + response.setProtobufPayload(testMessage.serializeBinary()); + } catch (err) { + response.setParseError(err.toString()); + } + } else if ( + request.getMessageType() === + 'protobuf_test_messages.editions.TestAllTypesEdition2023') { + try { + const testMessage = + TestAllTypesEdition2023.deserializeBinary(request.getProtobufPayload()); + response.setProtobufPayload(testMessage.serializeBinary()); + } catch (err) { + response.setParseError(err.toString()); + } + } else if ( + request.getMessageType() === + 'protobuf_test_messages.editions.proto2.TestAllTypesProto2') { + try { + const testMessage = + TestAllTypesProto2Edition2023.deserializeBinary(request.getProtobufPayload()); + response.setProtobufPayload(testMessage.serializeBinary()); + } catch (err) { + response.setParseError(err.toString()); + } + } else if ( + request.getMessageType() === + 'protobuf_test_messages.editions.proto3.TestAllTypesProto3') { + try { + const testMessage = + TestAllTypesProto3Edition2023.deserializeBinary(request.getProtobufPayload()); + response.setProtobufPayload(testMessage.serializeBinary()); + } catch (err) { + response.setParseError(err.toString()); + } + } else { + throw new Error( + `Payload message not supported: ${request.getMessageType()}.`); + } + + return response; +} + +/** + * Reads a buffer of N bytes. + * @param {number} bytes Number of bytes to read. + * @return {!Buffer} Buffer which contains data. + */ +function readBuffer(bytes) { + // Linux cannot use process.stdin.fd (which isn't set up as sync) + const buf = new Buffer.alloc(bytes); + const fd = fs.openSync('/dev/stdin', 'r'); + fs.readSync(fd, buf, 0, bytes); + fs.closeSync(fd); + return buf; +} + +/** + * Writes all data in buffer. + * @param {!Buffer} buffer Buffer which contains data. + */ +function writeBuffer(buffer) { + // Under linux, process.stdout.fd is async. Needs to open stdout in a synced + // way for sync write. + const fd = fs.openSync('/dev/stdout', 'w'); + fs.writeSync(fd, buffer, 0, buffer.length); + fs.closeSync(fd); +} + +/** + * Returns true if the test ran successfully, false on legitimate EOF. + * @return {boolean} Whether to continue test. + */ +function runConformanceTest() { + const requestLengthBuf = readBuffer(4); + const requestLength = requestLengthBuf.readInt32LE(0); + if (!requestLength) { + return false; + } + + const serializedRequest = readBuffer(requestLength); + const array = new Uint8Array(serializedRequest); + const request = ConformanceRequest.deserializeBinary(array.buffer); + const response = doTest(request); + + const serializedResponse = response.serializeBinary(); + + const responseLengthBuf = new Buffer.alloc(4); + responseLengthBuf.writeInt32LE(serializedResponse.byteLength, 0); + writeBuffer(responseLengthBuf); + writeBuffer(new Buffer.from(serializedResponse)); + + return true; +} + +while (true) { + if (!runConformanceTest()) { + break; + } +} diff --git a/experimental/runtime/kernel/conformance/conformance_request.js b/experimental/runtime/kernel/conformance/conformance_request.js deleted file mode 100644 index 2d4f106..0000000 --- a/experimental/runtime/kernel/conformance/conformance_request.js +++ /dev/null @@ -1,91 +0,0 @@ -/** - * @fileoverview Handwritten code of ConformanceRequest. - */ -goog.module('proto.conformance.ConformanceRequest'); - -const Kernel = goog.require('protobuf.runtime.Kernel'); -const WireFormat = goog.require('proto.conformance.WireFormat'); - -/** - * Handwritten code of conformance.ConformanceRequest. - * This is used to send request from the conformance test runner to the testee. - * Check //third_party/protobuf/testing/protobuf/conformance/conformance.proto - * for more details. - * @final - */ -class ConformanceRequest { - /** - * @param {!ArrayBuffer} bytes - * @private - */ - constructor(bytes) { - /** @private @const {!Kernel} */ - this.accessor_ = Kernel.fromArrayBuffer(bytes); - } - - /** - * Create a request instance with the given bytes data. - * @param {!ArrayBuffer} bytes - * @return {!ConformanceRequest} - */ - static deserialize(bytes) { - return new ConformanceRequest(bytes); - } - - /** - * Gets the protobuf_payload. - * @return {!ArrayBuffer} - */ - getProtobufPayload() { - return this.accessor_.getBytesWithDefault(1).toArrayBuffer(); - } - - /** - * Gets the requested_output_format. - * @return {!WireFormat} - */ - getRequestedOutputFormat() { - return /** @type {!WireFormat} */ (this.accessor_.getInt32WithDefault(3)); - } - - /** - * Gets the message_type. - * @return {string} - */ - getMessageType() { - return this.accessor_.getStringWithDefault(4); - } - - /** - * Gets the oneof case for payload field. - * This implementation assumes only one field in a oneof group is set. - * @return {!ConformanceRequest.PayloadCase} - */ - getPayloadCase() { - if (this.accessor_.hasFieldNumber(1)) { - return /** @type {!ConformanceRequest.PayloadCase} */ ( - ConformanceRequest.PayloadCase.PROTOBUF_PAYLOAD); - } else if (this.accessor_.hasFieldNumber(2)) { - return /** @type {!ConformanceRequest.PayloadCase} */ ( - ConformanceRequest.PayloadCase.JSON_PAYLOAD); - } else if (this.accessor_.hasFieldNumber(8)) { - return /** @type {!ConformanceRequest.PayloadCase} */ ( - ConformanceRequest.PayloadCase.TEXT_PAYLOAD); - } else { - return /** @type {!ConformanceRequest.PayloadCase} */ ( - ConformanceRequest.PayloadCase.PAYLOAD_NOT_SET); - } - } -} - -/** - * @enum {number} - */ -ConformanceRequest.PayloadCase = { - PAYLOAD_NOT_SET: 0, - PROTOBUF_PAYLOAD: 1, - JSON_PAYLOAD: 2, - TEXT_PAYLOAD: 8, -}; - -exports = ConformanceRequest; diff --git a/experimental/runtime/kernel/conformance/conformance_response.js b/experimental/runtime/kernel/conformance/conformance_response.js deleted file mode 100644 index 482f31b..0000000 --- a/experimental/runtime/kernel/conformance/conformance_response.js +++ /dev/null @@ -1,76 +0,0 @@ -/** - * @fileoverview Handwritten code of ConformanceResponse. - */ -goog.module('proto.conformance.ConformanceResponse'); - -const ByteString = goog.require('protobuf.ByteString'); -const Kernel = goog.require('protobuf.runtime.Kernel'); - -/** - * Handwritten code of conformance.ConformanceResponse. - * This is used to send response from the conformance testee to the test runner. - * Check //third_party/protobuf/testing/protobuf/conformance/conformance.proto - * for more details. - * @final - */ -class ConformanceResponse { - /** - * @param {!ArrayBuffer} bytes - * @private - */ - constructor(bytes) { - /** @private @const {!Kernel} */ - this.accessor_ = Kernel.fromArrayBuffer(bytes); - } - - /** - * Create an empty response instance. - * @return {!ConformanceResponse} - */ - static createEmpty() { - return new ConformanceResponse(new ArrayBuffer(0)); - } - - /** - * Sets parse_error field. - * @param {string} value - */ - setParseError(value) { - this.accessor_.setString(1, value); - } - - /** - * Sets runtime_error field. - * @param {string} value - */ - setRuntimeError(value) { - this.accessor_.setString(2, value); - } - - /** - * Sets protobuf_payload field. - * @param {!ArrayBuffer} value - */ - setProtobufPayload(value) { - const bytesString = ByteString.fromArrayBuffer(value); - this.accessor_.setBytes(3, bytesString); - } - - /** - * Sets skipped field. - * @param {string} value - */ - setSkipped(value) { - this.accessor_.setString(5, value); - } - - /** - * Serializes into binary data. - * @return {!ArrayBuffer} - */ - serialize() { - return this.accessor_.serialize(); - } -} - -exports = ConformanceResponse; diff --git a/experimental/runtime/kernel/conformance/conformance_testee.js b/experimental/runtime/kernel/conformance/conformance_testee.js deleted file mode 100755 index 2945228..0000000 --- a/experimental/runtime/kernel/conformance/conformance_testee.js +++ /dev/null @@ -1,103 +0,0 @@ -goog.module('javascript.protobuf.conformance'); - -const ConformanceRequest = goog.require('proto.conformance.ConformanceRequest'); -const ConformanceResponse = goog.require('proto.conformance.ConformanceResponse'); -const TestAllTypesProto2 = goog.require('proto.conformance.TestAllTypesProto2'); -const TestAllTypesProto3 = goog.require('proto.conformance.TestAllTypesProto3'); -const WireFormat = goog.require('proto.conformance.WireFormat'); -const base64 = goog.require('goog.crypt.base64'); - -/** - * Creates a `proto.conformance.ConformanceResponse` response according to the - * `proto.conformance.ConformanceRequest` request. - * @param {!ConformanceRequest} request - * @return {!ConformanceResponse} response - */ -function doTest(request) { - const response = ConformanceResponse.createEmpty(); - - if(request.getPayloadCase() === ConformanceRequest.PayloadCase.JSON_PAYLOAD) { - response.setSkipped('Json is not supported as input format.'); - return response; - } - - if(request.getPayloadCase() === ConformanceRequest.PayloadCase.TEXT_PAYLOAD) { - response.setSkipped('Text format is not supported as input format.'); - return response; - } - - if(request.getPayloadCase() === ConformanceRequest.PayloadCase.PAYLOAD_NOT_SET) { - response.setRuntimeError('Request didn\'t have payload.'); - return response; - } - - if(request.getPayloadCase() !== ConformanceRequest.PayloadCase.PROTOBUF_PAYLOAD) { - throw new Error('Request didn\'t have accepted input format.'); - } - - if (request.getRequestedOutputFormat() === WireFormat.JSON) { - response.setSkipped('Json is not supported as output format.'); - return response; - } - - if (request.getRequestedOutputFormat() === WireFormat.TEXT_FORMAT) { - response.setSkipped('Text format is not supported as output format.'); - return response; - } - - if (request.getRequestedOutputFormat() === WireFormat.TEXT_FORMAT) { - response.setRuntimeError('Unspecified output format'); - return response; - } - - if (request.getRequestedOutputFormat() !== WireFormat.PROTOBUF) { - throw new Error('Request didn\'t have accepted output format.'); - } - - if (request.getMessageType() === 'conformance.FailureSet') { - response.setProtobufPayload(new ArrayBuffer(0)); - } else if ( - request.getMessageType() === - 'protobuf_test_messages.proto2.TestAllTypesProto2') { - try { - const testMessage = - TestAllTypesProto2.deserialize(request.getProtobufPayload()); - response.setProtobufPayload(testMessage.serialize()); - } catch (err) { - response.setParseError(err.toString()); - } - } else if ( - request.getMessageType() === - 'protobuf_test_messages.proto3.TestAllTypesProto3') { - try { - const testMessage = - TestAllTypesProto3.deserialize(request.getProtobufPayload()); - response.setProtobufPayload(testMessage.serialize()); - } catch (err) { - response.setParseError(err.toString()); - } - } else { - throw new Error( - `Payload message not supported: ${request.getMessageType()}.`); - } - - return response; -} - -/** - * Same as doTest, but both request and response are in base64. - * @param {string} base64Request - * @return {string} response - */ -function runConformanceTest(base64Request) { - const request = - ConformanceRequest.deserialize( - base64.decodeStringToUint8Array(base64Request).buffer); - const response = doTest(request); - return base64.encodeByteArray(new Uint8Array(response.serialize())); -} - -// Needed for node test -exports.doTest = doTest; -// Needed for browser test -goog.exportSymbol('runConformanceTest', runConformanceTest); diff --git a/experimental/runtime/kernel/conformance/conformance_testee_runner_node.js b/experimental/runtime/kernel/conformance/conformance_testee_runner_node.js deleted file mode 100755 index c12f363..0000000 --- a/experimental/runtime/kernel/conformance/conformance_testee_runner_node.js +++ /dev/null @@ -1,62 +0,0 @@ -const ConformanceRequest = goog.require('proto.conformance.ConformanceRequest'); -const {doTest} = goog.require('javascript.protobuf.conformance'); -const fs = require('fs'); - - -/** - * Reads a buffer of N bytes. - * @param {number} bytes Number of bytes to read. - * @return {!Buffer} Buffer which contains data. - */ -function readBuffer(bytes) { - // Linux cannot use process.stdin.fd (which isn't set up as sync) - const buf = new Buffer.alloc(bytes); - const fd = fs.openSync('/dev/stdin', 'r'); - fs.readSync(fd, buf, 0, bytes); - fs.closeSync(fd); - return buf; -} - -/** - * Writes all data in buffer. - * @param {!Buffer} buffer Buffer which contains data. - */ -function writeBuffer(buffer) { - // Under linux, process.stdout.fd is async. Needs to open stdout in a synced - // way for sync write. - const fd = fs.openSync('/dev/stdout', 'w'); - fs.writeSync(fd, buffer, 0, buffer.length); - fs.closeSync(fd); -} - -/** - * Returns true if the test ran successfully, false on legitimate EOF. - * @return {boolean} Whether to continue test. - */ -function runConformanceTest() { - const requestLengthBuf = readBuffer(4); - const requestLength = requestLengthBuf.readInt32LE(0); - if (!requestLength) { - return false; - } - - const serializedRequest = readBuffer(requestLength); - const array = new Uint8Array(serializedRequest); - const request = ConformanceRequest.deserialize(array.buffer); - const response = doTest(request); - - const serializedResponse = response.serialize(); - - const responseLengthBuf = new Buffer.alloc(4); - responseLengthBuf.writeInt32LE(serializedResponse.byteLength, 0); - writeBuffer(responseLengthBuf); - writeBuffer(new Buffer.from(serializedResponse)); - - return true; -} - -while (true) { - if (!runConformanceTest()) { - break; - } -} diff --git a/experimental/runtime/kernel/conformance/test_all_types_proto2.js b/experimental/runtime/kernel/conformance/test_all_types_proto2.js deleted file mode 100644 index 3be1bee..0000000 --- a/experimental/runtime/kernel/conformance/test_all_types_proto2.js +++ /dev/null @@ -1,309 +0,0 @@ -/** - * @fileoverview Handwritten code of TestAllTypesProto2. - */ -goog.module('proto.conformance.TestAllTypesProto2'); - -const InternalMessage = goog.require('protobuf.binary.InternalMessage'); -const Kernel = goog.require('protobuf.runtime.Kernel'); - -/** - * Handwritten code of conformance.TestAllTypesProto2. - * Check google/protobuf/test_messages_proto3.proto for more details. - * @implements {InternalMessage} - * @final - */ -class TestAllTypesProto2 { - /** - * @param {!Kernel=} accessor - * @private - */ - constructor(accessor = Kernel.createEmpty()) { - /** @private @const {!Kernel} */ - this.accessor_ = accessor; - } - - /** - * @override - * @package - * @return {!Kernel} - */ - internalGetKernel() { - return this.accessor_; - } - - /** - * Create a request instance with the given bytes data. - * If we directly use the accessor created by the binary decoding, the - * Kernel instance will only copy the same data over for encoding. By - * explicitly fetching data from the previous accessor and setting all fields - * into a new accessor, we will actually test encoding/decoding for the binary - * format. - * @param {!ArrayBuffer} bytes - * @return {!TestAllTypesProto2} - */ - static deserialize(bytes) { - const msg = new TestAllTypesProto2(); - const requestAccessor = Kernel.fromArrayBuffer(bytes); - - if (requestAccessor.hasFieldNumber(1)) { - const value = requestAccessor.getInt32WithDefault(1); - msg.accessor_.setInt32(1, value); - } - - if (requestAccessor.hasFieldNumber(2)) { - const value = requestAccessor.getInt64WithDefault(2); - msg.accessor_.setInt64(2, value); - } - - if (requestAccessor.hasFieldNumber(3)) { - const value = requestAccessor.getUint32WithDefault(3); - msg.accessor_.setUint32(3, value); - } - - if (requestAccessor.hasFieldNumber(4)) { - const value = requestAccessor.getUint64WithDefault(4); - msg.accessor_.setUint64(4, value); - } - - if (requestAccessor.hasFieldNumber(5)) { - const value = requestAccessor.getSint32WithDefault(5); - msg.accessor_.setSint32(5, value); - } - - if (requestAccessor.hasFieldNumber(6)) { - const value = requestAccessor.getSint64WithDefault(6); - msg.accessor_.setSint64(6, value); - } - - if (requestAccessor.hasFieldNumber(7)) { - const value = requestAccessor.getFixed32WithDefault(7); - msg.accessor_.setFixed32(7, value); - } - - if (requestAccessor.hasFieldNumber(8)) { - const value = requestAccessor.getFixed64WithDefault(8); - msg.accessor_.setFixed64(8, value); - } - - if (requestAccessor.hasFieldNumber(9)) { - const value = requestAccessor.getSfixed32WithDefault(9); - msg.accessor_.setSfixed32(9, value); - } - - if (requestAccessor.hasFieldNumber(10)) { - const value = requestAccessor.getSfixed64WithDefault(10); - msg.accessor_.setSfixed64(10, value); - } - - if (requestAccessor.hasFieldNumber(11)) { - const value = requestAccessor.getFloatWithDefault(11); - msg.accessor_.setFloat(11, value); - } - - if (requestAccessor.hasFieldNumber(12)) { - const value = requestAccessor.getDoubleWithDefault(12); - msg.accessor_.setDouble(12, value); - } - - if (requestAccessor.hasFieldNumber(13)) { - const value = requestAccessor.getBoolWithDefault(13); - msg.accessor_.setBool(13, value); - } - - if (requestAccessor.hasFieldNumber(14)) { - const value = requestAccessor.getStringWithDefault(14); - msg.accessor_.setString(14, value); - } - - if (requestAccessor.hasFieldNumber(15)) { - const value = requestAccessor.getBytesWithDefault(15); - msg.accessor_.setBytes(15, value); - } - - if (requestAccessor.hasFieldNumber(18)) { - const value = requestAccessor.getMessage( - 18, (accessor) => new TestAllTypesProto2(accessor)); - msg.accessor_.setMessage(18, value); - } - - if (requestAccessor.hasFieldNumber(21)) { - // Unknown enum is not checked here, because even if an enum is unknown, - // it should be kept during encoding. For the purpose of wire format test, - // we can simplify the implementation by treating it as an int32 field, - // which has the same semantic except for the unknown value checking. - const value = requestAccessor.getInt32WithDefault(21); - msg.accessor_.setInt32(21, value); - } - - if (requestAccessor.hasFieldNumber(31)) { - const value = requestAccessor.getRepeatedInt32Iterable(31); - msg.accessor_.setUnpackedInt32Iterable(31, value); - } - - if (requestAccessor.hasFieldNumber(32)) { - const value = requestAccessor.getRepeatedInt64Iterable(32); - msg.accessor_.setUnpackedInt64Iterable(32, value); - } - - if (requestAccessor.hasFieldNumber(33)) { - const value = requestAccessor.getRepeatedUint32Iterable(33); - msg.accessor_.setUnpackedUint32Iterable(33, value); - } - - if (requestAccessor.hasFieldNumber(34)) { - const value = requestAccessor.getRepeatedUint64Iterable(34); - msg.accessor_.setUnpackedUint64Iterable(34, value); - } - - if (requestAccessor.hasFieldNumber(35)) { - const value = requestAccessor.getRepeatedSint32Iterable(35); - msg.accessor_.setUnpackedSint32Iterable(35, value); - } - - if (requestAccessor.hasFieldNumber(36)) { - const value = requestAccessor.getRepeatedSint64Iterable(36); - msg.accessor_.setUnpackedSint64Iterable(36, value); - } - - if (requestAccessor.hasFieldNumber(37)) { - const value = requestAccessor.getRepeatedFixed32Iterable(37); - msg.accessor_.setUnpackedFixed32Iterable(37, value); - } - - if (requestAccessor.hasFieldNumber(38)) { - const value = requestAccessor.getRepeatedFixed64Iterable(38); - msg.accessor_.setUnpackedFixed64Iterable(38, value); - } - - if (requestAccessor.hasFieldNumber(39)) { - const value = requestAccessor.getRepeatedSfixed32Iterable(39); - msg.accessor_.setUnpackedSfixed32Iterable(39, value); - } - - if (requestAccessor.hasFieldNumber(40)) { - const value = requestAccessor.getRepeatedSfixed64Iterable(40); - msg.accessor_.setUnpackedSfixed64Iterable(40, value); - } - - if (requestAccessor.hasFieldNumber(41)) { - const value = requestAccessor.getRepeatedFloatIterable(41); - msg.accessor_.setUnpackedFloatIterable(41, value); - } - - if (requestAccessor.hasFieldNumber(42)) { - const value = requestAccessor.getRepeatedDoubleIterable(42); - msg.accessor_.setUnpackedDoubleIterable(42, value); - } - - if (requestAccessor.hasFieldNumber(43)) { - const value = requestAccessor.getRepeatedBoolIterable(43); - msg.accessor_.setUnpackedBoolIterable(43, value); - } - - if (requestAccessor.hasFieldNumber(44)) { - const value = requestAccessor.getRepeatedStringIterable(44); - msg.accessor_.setRepeatedStringIterable(44, value); - } - - if (requestAccessor.hasFieldNumber(45)) { - const value = requestAccessor.getRepeatedBytesIterable(45); - msg.accessor_.setRepeatedBytesIterable(45, value); - } - - if (requestAccessor.hasFieldNumber(48)) { - const value = requestAccessor.getRepeatedMessageIterable( - 48, (accessor) => new TestAllTypesProto2(accessor)); - msg.accessor_.setRepeatedMessageIterable(48, value); - } - - if (requestAccessor.hasFieldNumber(51)) { - // Unknown enum is not checked here, because even if an enum is unknown, - // it should be kept during encoding. For the purpose of wire format test, - // we can simplify the implementation by treating it as an int32 field, - // which has the same semantic except for the unknown value checking. - const value = requestAccessor.getRepeatedInt32Iterable(51); - msg.accessor_.setUnpackedInt32Iterable(51, value); - } - - if (requestAccessor.hasFieldNumber(75)) { - const value = requestAccessor.getRepeatedInt32Iterable(75); - msg.accessor_.setPackedInt32Iterable(75, value); - } - - if (requestAccessor.hasFieldNumber(76)) { - const value = requestAccessor.getRepeatedInt64Iterable(76); - msg.accessor_.setPackedInt64Iterable(76, value); - } - - if (requestAccessor.hasFieldNumber(77)) { - const value = requestAccessor.getRepeatedUint32Iterable(77); - msg.accessor_.setPackedUint32Iterable(77, value); - } - - if (requestAccessor.hasFieldNumber(78)) { - const value = requestAccessor.getRepeatedUint64Iterable(78); - msg.accessor_.setPackedUint64Iterable(78, value); - } - - if (requestAccessor.hasFieldNumber(79)) { - const value = requestAccessor.getRepeatedSint32Iterable(79); - msg.accessor_.setPackedSint32Iterable(79, value); - } - - if (requestAccessor.hasFieldNumber(80)) { - const value = requestAccessor.getRepeatedSint64Iterable(80); - msg.accessor_.setPackedSint64Iterable(80, value); - } - - if (requestAccessor.hasFieldNumber(81)) { - const value = requestAccessor.getRepeatedFixed32Iterable(81); - msg.accessor_.setPackedFixed32Iterable(81, value); - } - - if (requestAccessor.hasFieldNumber(82)) { - const value = requestAccessor.getRepeatedFixed64Iterable(82); - msg.accessor_.setPackedFixed64Iterable(82, value); - } - - if (requestAccessor.hasFieldNumber(83)) { - const value = requestAccessor.getRepeatedSfixed32Iterable(83); - msg.accessor_.setPackedSfixed32Iterable(83, value); - } - - if (requestAccessor.hasFieldNumber(84)) { - const value = requestAccessor.getRepeatedSfixed64Iterable(84); - msg.accessor_.setPackedSfixed64Iterable(84, value); - } - - if (requestAccessor.hasFieldNumber(85)) { - const value = requestAccessor.getRepeatedFloatIterable(85); - msg.accessor_.setPackedFloatIterable(85, value); - } - - if (requestAccessor.hasFieldNumber(86)) { - const value = requestAccessor.getRepeatedDoubleIterable(86); - msg.accessor_.setPackedDoubleIterable(86, value); - } - - if (requestAccessor.hasFieldNumber(87)) { - const value = requestAccessor.getRepeatedBoolIterable(87); - msg.accessor_.setPackedBoolIterable(87, value); - } - - if (requestAccessor.hasFieldNumber(88)) { - const value = requestAccessor.getRepeatedInt32Iterable(88); - msg.accessor_.setPackedInt32Iterable(88, value); - } - return msg; - } - - /** - * Serializes into binary data. - * @return {!ArrayBuffer} - */ - serialize() { - return this.accessor_.serialize(); - } -} - -exports = TestAllTypesProto2; diff --git a/experimental/runtime/kernel/conformance/test_all_types_proto3.js b/experimental/runtime/kernel/conformance/test_all_types_proto3.js deleted file mode 100644 index c68d370..0000000 --- a/experimental/runtime/kernel/conformance/test_all_types_proto3.js +++ /dev/null @@ -1,310 +0,0 @@ -/** - * @fileoverview Handwritten code of TestAllTypesProto3. - */ -goog.module('proto.conformance.TestAllTypesProto3'); - -const InternalMessage = goog.require('protobuf.binary.InternalMessage'); -const Kernel = goog.require('protobuf.runtime.Kernel'); - -/** - * Handwritten code of conformance.TestAllTypesProto3. - * Check google/protobuf/test_messages_proto3.proto for more details. - * @implements {InternalMessage} - * @final - */ -class TestAllTypesProto3 { - /** - * @param {!Kernel=} accessor - * @private - */ - constructor(accessor = Kernel.createEmpty()) { - /** @private @const {!Kernel} */ - this.accessor_ = accessor; - } - - /** - * @override - * @package - * @return {!Kernel} - */ - internalGetKernel() { - return this.accessor_; - } - - /** - * Create a request instance with the given bytes data. - * If we directly use the accessor created by the binary decoding, the - * Kernel instance will only copy the same data over for encoding. By - * explicitly fetching data from the previous accessor and setting all fields - * into a new accessor, we will actually test encoding/decoding for the binary - * format. - * @param {!ArrayBuffer} bytes - * @return {!TestAllTypesProto3} - */ - static deserialize(bytes) { - const msg = new TestAllTypesProto3(); - const requestAccessor = Kernel.fromArrayBuffer(bytes); - - if (requestAccessor.hasFieldNumber(1)) { - const value = requestAccessor.getInt32WithDefault(1); - msg.accessor_.setInt32(1, value); - } - - if (requestAccessor.hasFieldNumber(2)) { - const value = requestAccessor.getInt64WithDefault(2); - msg.accessor_.setInt64(2, value); - } - - if (requestAccessor.hasFieldNumber(3)) { - const value = requestAccessor.getUint32WithDefault(3); - msg.accessor_.setUint32(3, value); - } - - if (requestAccessor.hasFieldNumber(4)) { - const value = requestAccessor.getUint64WithDefault(4); - msg.accessor_.setUint64(4, value); - } - - if (requestAccessor.hasFieldNumber(5)) { - const value = requestAccessor.getSint32WithDefault(5); - msg.accessor_.setSint32(5, value); - } - - if (requestAccessor.hasFieldNumber(6)) { - const value = requestAccessor.getSint64WithDefault(6); - msg.accessor_.setSint64(6, value); - } - - if (requestAccessor.hasFieldNumber(7)) { - const value = requestAccessor.getFixed32WithDefault(7); - msg.accessor_.setFixed32(7, value); - } - - if (requestAccessor.hasFieldNumber(8)) { - const value = requestAccessor.getFixed64WithDefault(8); - msg.accessor_.setFixed64(8, value); - } - - if (requestAccessor.hasFieldNumber(9)) { - const value = requestAccessor.getSfixed32WithDefault(9); - msg.accessor_.setSfixed32(9, value); - } - - if (requestAccessor.hasFieldNumber(10)) { - const value = requestAccessor.getSfixed64WithDefault(10); - msg.accessor_.setSfixed64(10, value); - } - - if (requestAccessor.hasFieldNumber(11)) { - const value = requestAccessor.getFloatWithDefault(11); - msg.accessor_.setFloat(11, value); - } - - if (requestAccessor.hasFieldNumber(12)) { - const value = requestAccessor.getDoubleWithDefault(12); - msg.accessor_.setDouble(12, value); - } - - if (requestAccessor.hasFieldNumber(13)) { - const value = requestAccessor.getBoolWithDefault(13); - msg.accessor_.setBool(13, value); - } - - if (requestAccessor.hasFieldNumber(14)) { - const value = requestAccessor.getStringWithDefault(14); - msg.accessor_.setString(14, value); - } - - if (requestAccessor.hasFieldNumber(15)) { - const value = requestAccessor.getBytesWithDefault(15); - msg.accessor_.setBytes(15, value); - } - - if (requestAccessor.hasFieldNumber(18)) { - const value = requestAccessor.getMessage( - 18, (accessor) => new TestAllTypesProto3(accessor)); - msg.accessor_.setMessage(18, value); - } - - if (requestAccessor.hasFieldNumber(21)) { - // Unknown enum is not checked here, because even if an enum is unknown, - // it should be kept during encoding. For the purpose of wire format test, - // we can simplify the implementation by treating it as an int32 field, - // which has the same semantic except for the unknown value checking. - const value = requestAccessor.getInt32WithDefault(21); - msg.accessor_.setInt32(21, value); - } - - if (requestAccessor.hasFieldNumber(31)) { - const value = requestAccessor.getRepeatedInt32Iterable(31); - msg.accessor_.setPackedInt32Iterable(31, value); - } - - if (requestAccessor.hasFieldNumber(32)) { - const value = requestAccessor.getRepeatedInt64Iterable(32); - msg.accessor_.setPackedInt64Iterable(32, value); - } - - if (requestAccessor.hasFieldNumber(33)) { - const value = requestAccessor.getRepeatedUint32Iterable(33); - msg.accessor_.setPackedUint32Iterable(33, value); - } - - if (requestAccessor.hasFieldNumber(34)) { - const value = requestAccessor.getRepeatedUint64Iterable(34); - msg.accessor_.setPackedUint64Iterable(34, value); - } - - if (requestAccessor.hasFieldNumber(35)) { - const value = requestAccessor.getRepeatedSint32Iterable(35); - msg.accessor_.setPackedSint32Iterable(35, value); - } - - if (requestAccessor.hasFieldNumber(36)) { - const value = requestAccessor.getRepeatedSint64Iterable(36); - msg.accessor_.setPackedSint64Iterable(36, value); - } - - if (requestAccessor.hasFieldNumber(37)) { - const value = requestAccessor.getRepeatedFixed32Iterable(37); - msg.accessor_.setPackedFixed32Iterable(37, value); - } - - if (requestAccessor.hasFieldNumber(38)) { - const value = requestAccessor.getRepeatedFixed64Iterable(38); - msg.accessor_.setPackedFixed64Iterable(38, value); - } - - if (requestAccessor.hasFieldNumber(39)) { - const value = requestAccessor.getRepeatedSfixed32Iterable(39); - msg.accessor_.setPackedSfixed32Iterable(39, value); - } - - if (requestAccessor.hasFieldNumber(40)) { - const value = requestAccessor.getRepeatedSfixed64Iterable(40); - msg.accessor_.setPackedSfixed64Iterable(40, value); - } - - if (requestAccessor.hasFieldNumber(41)) { - const value = requestAccessor.getRepeatedFloatIterable(41); - msg.accessor_.setPackedFloatIterable(41, value); - } - - if (requestAccessor.hasFieldNumber(42)) { - const value = requestAccessor.getRepeatedDoubleIterable(42); - msg.accessor_.setPackedDoubleIterable(42, value); - } - - if (requestAccessor.hasFieldNumber(43)) { - const value = requestAccessor.getRepeatedBoolIterable(43); - msg.accessor_.setPackedBoolIterable(43, value); - } - - if (requestAccessor.hasFieldNumber(44)) { - const value = requestAccessor.getRepeatedStringIterable(44); - msg.accessor_.setRepeatedStringIterable(44, value); - } - - if (requestAccessor.hasFieldNumber(45)) { - const value = requestAccessor.getRepeatedBytesIterable(45); - msg.accessor_.setRepeatedBytesIterable(45, value); - } - - if (requestAccessor.hasFieldNumber(48)) { - const value = requestAccessor.getRepeatedMessageIterable( - 48, (accessor) => new TestAllTypesProto3(accessor)); - msg.accessor_.setRepeatedMessageIterable(48, value); - } - - if (requestAccessor.hasFieldNumber(51)) { - // Unknown enum is not checked here, because even if an enum is unknown, - // it should be kept during encoding. For the purpose of wire format test, - // we can simplify the implementation by treating it as an int32 field, - // which has the same semantic except for the unknown value checking. - const value = requestAccessor.getRepeatedInt32Iterable(51); - msg.accessor_.setPackedInt32Iterable(51, value); - } - - if (requestAccessor.hasFieldNumber(89)) { - const value = requestAccessor.getRepeatedInt32Iterable(89); - msg.accessor_.setUnpackedInt32Iterable(89, value); - } - - if (requestAccessor.hasFieldNumber(90)) { - const value = requestAccessor.getRepeatedInt64Iterable(90); - msg.accessor_.setUnpackedInt64Iterable(90, value); - } - - if (requestAccessor.hasFieldNumber(91)) { - const value = requestAccessor.getRepeatedUint32Iterable(91); - msg.accessor_.setUnpackedUint32Iterable(91, value); - } - - if (requestAccessor.hasFieldNumber(92)) { - const value = requestAccessor.getRepeatedUint64Iterable(92); - msg.accessor_.setUnpackedUint64Iterable(92, value); - } - - if (requestAccessor.hasFieldNumber(93)) { - const value = requestAccessor.getRepeatedSint32Iterable(93); - msg.accessor_.setUnpackedSint32Iterable(93, value); - } - - if (requestAccessor.hasFieldNumber(94)) { - const value = requestAccessor.getRepeatedSint64Iterable(94); - msg.accessor_.setUnpackedSint64Iterable(94, value); - } - - if (requestAccessor.hasFieldNumber(95)) { - const value = requestAccessor.getRepeatedFixed32Iterable(95); - msg.accessor_.setUnpackedFixed32Iterable(95, value); - } - - if (requestAccessor.hasFieldNumber(96)) { - const value = requestAccessor.getRepeatedFixed64Iterable(96); - msg.accessor_.setUnpackedFixed64Iterable(96, value); - } - - if (requestAccessor.hasFieldNumber(97)) { - const value = requestAccessor.getRepeatedSfixed32Iterable(97); - msg.accessor_.setUnpackedSfixed32Iterable(97, value); - } - - if (requestAccessor.hasFieldNumber(98)) { - const value = requestAccessor.getRepeatedSfixed64Iterable(98); - msg.accessor_.setUnpackedSfixed64Iterable(98, value); - } - - if (requestAccessor.hasFieldNumber(99)) { - const value = requestAccessor.getRepeatedFloatIterable(99); - msg.accessor_.setUnpackedFloatIterable(99, value); - } - - if (requestAccessor.hasFieldNumber(100)) { - const value = requestAccessor.getRepeatedDoubleIterable(100); - msg.accessor_.setUnpackedDoubleIterable(100, value); - } - - if (requestAccessor.hasFieldNumber(101)) { - const value = requestAccessor.getRepeatedBoolIterable(101); - msg.accessor_.setUnpackedBoolIterable(101, value); - } - - if (requestAccessor.hasFieldNumber(102)) { - const value = requestAccessor.getRepeatedInt32Iterable(102); - msg.accessor_.setUnpackedInt32Iterable(102, value); - } - - return msg; - } - - /** - * Serializes into binary data. - * @return {!ArrayBuffer} - */ - serialize() { - return this.accessor_.serialize(); - } -} - -exports = TestAllTypesProto3; diff --git a/experimental/runtime/kernel/conformance/wire_format.js b/experimental/runtime/kernel/conformance/wire_format.js deleted file mode 100644 index 636e827..0000000 --- a/experimental/runtime/kernel/conformance/wire_format.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * @fileoverview Handwritten code of WireFormat. - */ -goog.module('proto.conformance.WireFormat'); - -/** - * @enum {number} - */ -const WireFormat = { - UNSPECIFIED: 0, - PROTOBUF: 1, - JSON: 2, - TEXT_FORMAT: 4, -}; - -exports = WireFormat; diff --git a/experimental/runtime/kernel/kernel_repeated_test.js b/experimental/runtime/kernel/kernel_repeated_test.js index 6a798b6..059f5fc 100644 --- a/experimental/runtime/kernel/kernel_repeated_test.js +++ b/experimental/runtime/kernel/kernel_repeated_test.js @@ -1,7 +1,7 @@ /** * @fileoverview Tests for repeated methods in kernel.js. */ -goog.module('protobuf.runtime.KernelTest'); +goog.module('protobuf.runtime.KernelRepeatedTest'); goog.setTestOnly(); diff --git a/generator/js_generator.cc b/generator/js_generator.cc index a881c3d..0098f29 100644 --- a/generator/js_generator.cc +++ b/generator/js_generator.cc @@ -1087,14 +1087,17 @@ std::string JSBinaryMethodType(const FieldDescriptor* field, bool is_writer) { std::string JSBinaryReadWriteMethodName(const FieldDescriptor* field, bool is_writer) { std::string name = JSBinaryMethodType(field, is_writer); - if (field->is_packed()) { + if (is_writer && field->is_packed()) { name = "Packed" + name; + } else if (!is_writer && field->is_packable()) { + name = "Packable" + name + "Into"; } else if (is_writer && field->is_repeated()) { name = "Repeated" + name; } return name; } + std::string JSBinaryReaderMethodName(const GeneratorOptions& options, const FieldDescriptor* field) { return "jspb.BinaryReader.prototype.read" + @@ -1857,6 +1860,7 @@ void Generator::GenerateRequiresImpl(const GeneratorOptions& options, bool require_map) const { if (require_jspb) { required->insert("jspb.Message"); + required->insert("jspb.internal.public_for_gencode"); required->insert("jspb.BinaryReader"); required->insert("jspb.BinaryWriter"); } @@ -3129,13 +3133,11 @@ void Generator::GenerateClassDeserializeBinaryField( : ""); } else if (field->is_packable()) { printer->Print( - " var values = /** @type {$fieldtype$} */ " - "(reader.isDelimited() " - "? reader.read$reader$() : [reader.read$reader$()]);\n", - "fieldtype", - JSFieldTypeAnnotation(options, field, false, true, - /* singular_if_not_packed */ false, BYTES_U8), - "reader", JSBinaryReadWriteMethodName(field, /* is_writer=*/false)); + " reader.read$reader$(msg.get$name$());\n", "reader", + JSBinaryReadWriteMethodName(field, /* is_writer=*/false), "name", + JSGetterName(options, field, BYTES_DEFAULT, /* drop_list = */ false)); + printer->Print(" break;\n"); + return; } else { printer->Print( " var value = /** @type {$fieldtype$} */ " @@ -3147,14 +3149,7 @@ void Generator::GenerateClassDeserializeBinaryField( JSBinaryReadWriteMethodName(field, /* is_writer = */ false)); } - if (field->is_packable()) { - printer->Print( - " for (var i = 0; i < values.length; i++) {\n" - " msg.add$name$(values[i]);\n" - " }\n", - "name", - JSGetterName(options, field, BYTES_DEFAULT, /* drop_list = */ true)); - } else if (field->is_repeated()) { + if (field->is_repeated()) { printer->Print( " msg.add$name$(value);\n", "name", JSGetterName(options, field, BYTES_DEFAULT, /* drop_list = */ true)); @@ -3217,6 +3212,30 @@ void Generator::GenerateClassSerializeBinary(const GeneratorOptions& options, "\n"); } +// Generates the code to access a single field value for the binary serializer. +void GenerateClassSerializeBinaryFieldAccess(const GeneratorOptions& options, + io::Printer* printer, + const FieldDescriptor* field) { + if (field->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE) { + std::string typed_annotation = + JSFieldTypeAnnotation(options, field, + /* is_setter_argument = */ false, + /* force_present = */ false, + /* singular_if_not_packed = */ false, + /* bytes_mode = */ BYTES_DEFAULT); + printer->Print( + " /** @type {$type$} */ " + "(message.internal_getField($index$))", + "index", JSFieldIndex(field), "type", typed_annotation); + } else { + printer->Print( + " message.get$name$($nolazy$)", "name", + JSGetterName(options, field, BYTES_U8), + // No lazy creation for maps containers -- fastpath the empty case. + "nolazy", field->is_map() ? "true" : ""); + } +} + void Generator::GenerateClassSerializeBinaryField( const GeneratorOptions& options, io::Printer* printer, const FieldDescriptor* field) const { @@ -3292,15 +3311,20 @@ void Generator::GenerateClassSerializeBinaryField( if (field->is_map()) { const FieldDescriptor* key_field = MapFieldKey(field); const FieldDescriptor* value_field = MapFieldValue(field); + printer->Print("jspb.internal.public_for_gencode.serializeMapToBinary(\n"); + GenerateClassSerializeBinaryFieldAccess(options, printer, field); printer->Print( - " f.serializeBinary($index$, writer, " - "$keyWriterFn$, $valueWriterFn$", + ",\n $index$,\n" + " writer,\n" + " $keyWriterFn$,\n" + " $valueWriterFn$", "index", absl::StrCat(field->number()), "keyWriterFn", JSBinaryWriterMethodName(options, key_field), "valueWriterFn", JSBinaryWriterMethodName(options, value_field)); if (value_field->type() == FieldDescriptor::TYPE_MESSAGE) { - printer->Print(", $messageType$.serializeBinaryToWriter", "messageType", + printer->Print(",\n $messageType$.serializeBinaryToWriter", + "messageType", GetMessagePath(options, value_field->message_type())); } @@ -3429,7 +3453,7 @@ void Generator::GenerateExtension(const GeneratorOptions& options, : "undefined"); printer->Print(" $isPacked$);\n", "isPacked", - (field->is_packed() ? "true" : "false")); + (field->is_packable() ? "true" : "false")); printer->Print( "// This registers the extension field with the extended class, so that\n" diff --git a/generator/js_generator.h b/generator/js_generator.h index 98b8126..60a5221 100644 --- a/generator/js_generator.h +++ b/generator/js_generator.h @@ -151,9 +151,12 @@ class Generator : public CodeGenerator { std::string* error) const override; uint64_t GetSupportedFeatures() const override { - return FEATURE_PROTO3_OPTIONAL; + return FEATURE_PROTO3_OPTIONAL | FEATURE_SUPPORTS_EDITIONS; } + Edition GetMinimumEdition() const override { return Edition::EDITION_PROTO2; } + Edition GetMaximumEdition() const override { return Edition::EDITION_2023; } + private: void GenerateHeader(const GeneratorOptions& options, const FileDescriptor* file, io::Printer* printer) const; diff --git a/gulpfile.js b/gulpfile.js index 426e0f1..3703193 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,4 +1,4 @@ -const {series} = require('gulp'); +const { series } = require('gulp'); const execFile = require('child_process').execFile; const glob = require('glob'); @@ -6,10 +6,15 @@ function exec(command, cb) { execFile('sh', ['-c', command], cb); } -const plugin = '--plugin=protoc-gen-js=bazel-bin/generator/protoc-gen-js'; +const plugin = '--plugin=protoc-gen-js=bazel-bin/generator/protoc-gen-js'; const protoc = [(process.env.PROTOC || 'protoc'), plugin].join(' '); const protocInc = process.env.PROTOC_INC || '../src'; +// Obtained by running bazel build //conformance:conformance_test_runner in +// https://github.com/protocolbuffers/protobuf under +// bazel-bin/conformance/conformance_test_runner +const protoConfonformanceRunner = process.env.PROTO_CONFORMANCE_RUNNER || 'conformance_test_runner'; + // See https://github.com/google/closure-compiler/wiki/Flags-and-Options let compilationLevel = 'SIMPLE'; @@ -59,6 +64,15 @@ const group3Protos = [ 'protos/test10.proto' ]; +const conformanceProtos = [ + 'conformance/protos/conformance.proto', + 'conformance/protos/test_messages_proto2.proto', + 'conformance/protos/test_messages_proto3.proto', + 'conformance/protos/test_messages_edition2023.proto', + 'conformance/protos/test_messages_proto2_editions.proto', + 'conformance/protos/test_messages_proto3_editions.proto' +]; + function make_exec_logging_callback(cb) { return (err, stdout, stderr) => { console.log(stdout); @@ -68,7 +82,7 @@ function make_exec_logging_callback(cb) { } function enableAdvancedOptimizations(cb) { - compilationLevel = 'ADVANCED'; + compilationLevel = 'SIMPLE'; cb(); } @@ -79,21 +93,21 @@ function enableSimpleOptimizations(cb) { function genproto_well_known_types_closure(cb) { exec(protoc + ' --js_out=one_output_file_per_input_file,binary:. -I ' + protocInc + ' -I . ' + wellKnownTypes.join(' '), - make_exec_logging_callback(cb)); + make_exec_logging_callback(cb)); } function genproto_group1_closure(cb) { exec(protoc + ' --js_out=library=testproto_libs1,binary:. -I ' + protocInc + ' -I . ' + group1Protos.join(' '), - make_exec_logging_callback(cb)); + make_exec_logging_callback(cb)); } function genproto_group2_closure(cb) { exec( - protoc + - ' --experimental_allow_proto3_optional' + - ' --js_out=library=testproto_libs2,binary:. -I ' + protocInc + ' -I . -I commonjs ' + - group2Protos.join(' '), - make_exec_logging_callback(cb)); + protoc + + ' --experimental_allow_proto3_optional' + + ' --js_out=library=testproto_libs2,binary:. -I ' + protocInc + ' -I . -I commonjs ' + + group2Protos.join(' '), + make_exec_logging_callback(cb)); } function genproto_well_known_types_commonjs(cb) { @@ -101,32 +115,52 @@ function genproto_well_known_types_commonjs(cb) { make_exec_logging_callback(cb)); } +function genproto_conformance_commonjs(cb) { + exec(`${protoc} --js_out=import_style=commonjs,binary:conformance/protos -I ${protocInc} -I conformance/protos ${conformanceProtos.join(' ')}`, + make_exec_logging_callback(cb)); +} + +function pack_google_protobuf(cb) { + exec('npm pack', + make_exec_logging_callback(cb)); +} + +function install_conformance_test_deps(cb) { + exec('cd conformance && npm install ../google-protobuf-3.21.4.tgz', + make_exec_logging_callback(cb)); +} + +function run_conformance_tests(cb) { + exec(`${protoConfonformanceRunner} --enforce_recommended --maximum_edition 2023 --output_dir conformance/ conformance/runner.js`, + make_exec_logging_callback(cb)); +} + function genproto_group1_commonjs(cb) { - exec('mkdir -p commonjs_out && ' + protoc + ' --js_out=import_style=commonjs,binary:commonjs_out -I ' + protocInc + ' -I commonjs -I . ' + group1Protos.join(' '), - make_exec_logging_callback(cb)); + exec('mkdir -p commonjs_out && ' + protoc + ' --js_out=import_style=commonjs,binary:commonjs_out -I ' + protocInc + ' -I commonjs -I . ' + group1Protos.join(' '), + make_exec_logging_callback(cb)); } function genproto_group2_commonjs(cb) { exec( - 'mkdir -p commonjs_out && ' + protoc + - ' --experimental_allow_proto3_optional --js_out=import_style=commonjs,binary:commonjs_out -I ' + protocInc + ' -I commonjs -I . ' + - group2Protos.join(' '), - make_exec_logging_callback(cb)); + 'mkdir -p commonjs_out && ' + protoc + + ' --experimental_allow_proto3_optional --js_out=import_style=commonjs,binary:commonjs_out -I ' + protocInc + ' -I commonjs -I . ' + + group2Protos.join(' '), + make_exec_logging_callback(cb)); } function genproto_commonjs_wellknowntypes(cb) { - exec('mkdir -p commonjs_out/node_modules/google-protobuf && ' + protoc + ' --js_out=import_style=commonjs,binary:commonjs_out/node_modules/google-protobuf -I ' + protocInc + ' ' + wellKnownTypes.join(' '), - make_exec_logging_callback(cb)); + exec('mkdir -p commonjs_out/node_modules/google-protobuf && ' + protoc + ' --js_out=import_style=commonjs,binary:commonjs_out/node_modules/google-protobuf -I ' + protocInc + ' ' + wellKnownTypes.join(' '), + make_exec_logging_callback(cb)); } function genproto_wellknowntypes(cb) { - exec(protoc + ' --js_out=import_style=commonjs,binary:. -I ' + protocInc + ' ' + wellKnownTypes.join(' '), - make_exec_logging_callback(cb)); + exec(protoc + ' --js_out=import_style=commonjs,binary:. -I ' + protocInc + ' ' + wellKnownTypes.join(' '), + make_exec_logging_callback(cb)); } function genproto_group3_commonjs_strict(cb) { - exec('mkdir -p commonjs_out && ' + protoc + ' --js_out=import_style=commonjs_strict,binary:commonjs_out -I ' + protocInc + ' -I commonjs -I . ' + group3Protos.join(' '), - make_exec_logging_callback(cb)); + exec('mkdir -p commonjs_out && ' + protoc + ' --js_out=import_style=commonjs_strict,binary:commonjs_out -I ' + protocInc + ' -I commonjs -I . ' + group3Protos.join(' '), + make_exec_logging_callback(cb)); } @@ -138,16 +172,15 @@ function getClosureCompilerCommand(exportsFile, outputFile) { `--js=${closureLib}/third_party/closure/goog/**.js`, '--js=asserts.js', '--js=debug.js', + '--js=internal_bytes.js', + '--js=internal_options.js', + '--js=internal_public.js', '--js=map.js', '--js=message.js', - '--js=binary/arith.js', - '--js=binary/constants.js', - '--js=binary/decoder.js', - '--js=binary/encoder.js', - '--js=binary/reader.js', - '--js=binary/utf8.js', - '--js=binary/utils.js', - '--js=binary/writer.js', + '--js=bytestring.js', + '--js=unsafe_bytestring.js', + '--js=binary/**.js', + '--js=!binary/**_test.js', `--js=${exportsFile}`, '--generate_exports', `--compilation_level=${compilationLevel}`, @@ -159,115 +192,116 @@ function getClosureCompilerCommand(exportsFile, outputFile) { function gen_google_protobuf_js(cb) { exec( - getClosureCompilerCommand('commonjs/export.js', 'google-protobuf.js'), - make_exec_logging_callback(cb)); + getClosureCompilerCommand('commonjs/export.js', 'google-protobuf.js'), + make_exec_logging_callback(cb)); } function commonjs_testdeps(cb) { - exec( - 'mkdir -p commonjs_out/test_node_modules && ' + - getClosureCompilerCommand( - 'commonjs/export_testdeps.js', - 'commonjs_out/test_node_modules/testdeps_commonjs.js'), - make_exec_logging_callback(cb)); + exec( + 'mkdir -p commonjs_out/test_node_modules && ' + + getClosureCompilerCommand( + 'commonjs/export_testdeps.js', + 'commonjs_out/test_node_modules/testdeps_commonjs.js'), + make_exec_logging_callback(cb)); } function commonjs_out(cb) { - let cmd = - 'mkdir -p commonjs_out/binary && mkdir -p commonjs_out/test_node_modules && '; - function addTestFile(file) { - cmd += 'node commonjs/rewrite_tests_for_commonjs.js < ' + file + - ' > commonjs_out/' + file + '&& '; - } - - glob.sync('*_test.js').forEach(addTestFile); - glob.sync('binary/*_test.js').forEach(addTestFile); - - exec( - cmd + 'cp commonjs/jasmine.json commonjs_out/jasmine.json && ' + - 'cp google-protobuf.js commonjs_out/test_node_modules && ' + - 'cp commonjs/strict_test.js commonjs_out/strict_test.js &&' + - 'cp commonjs/import_test.js commonjs_out/import_test.js', - make_exec_logging_callback(cb)); + let cmd = + 'mkdir -p commonjs_out/binary && mkdir -p commonjs_out/test_node_modules && '; + function addTestFile(file) { + cmd += 'node commonjs/rewrite_tests_for_commonjs.js < ' + file + + ' > commonjs_out/' + file + '&& '; + } + + glob.sync('*_test.js').forEach(addTestFile); + glob.sync('binary/*_test.js').forEach(addTestFile); + + exec( + cmd + 'cp commonjs/jasmine.json commonjs_out/jasmine.json && ' + + 'cp google-protobuf.js commonjs_out/test_node_modules && ' + + 'cp commonjs/strict_test.js commonjs_out/strict_test.js &&' + + 'cp commonjs/import_test.js commonjs_out/import_test.js', + make_exec_logging_callback(cb)); } function closure_make_deps(cb) { exec( - './node_modules/.bin/closure-make-deps --closure-path=. --file=node_modules/google-closure-library/closure/goog/deps.js binary/arith.js binary/constants.js binary/decoder.js binary/encoder.js binary/reader.js binary/utf8.js binary/utils.js binary/writer.js asserts.js debug.js map.js message.js node_loader.js test_bootstrap.js > deps.js', - make_exec_logging_callback(cb)); + './node_modules/.bin/closure-make-deps --closure-path=. --file=node_modules/google-closure-library/closure/goog/deps.js bytestring.js internal_bytes.js internal_options.js internal_public.js binary/arith.js binary/bytesource.js binary/binary_constants.js binary/decoder.js binary/decoder_alias.js binary/encoder.js binary/encoder_alias.js binary/errors.js binary/internal_buffer.js binary/reader.js binary/reader_alias.js binary/test_utils.js binary/utf8.js binary/utils.js binary/writer.js binary/writer_alias.js asserts.js debug.js map.js message.js node_loader.js test_bootstrap.js unsafe_bytestring.js > deps.js', + make_exec_logging_callback(cb)); } function test_closure(cb) { exec( - 'JASMINE_CONFIG_PATH=jasmine.json ./node_modules/.bin/jasmine', - make_exec_logging_callback(cb)); + 'JASMINE_CONFIG_PATH=jasmine.json ./node_modules/.bin/jasmine --random=false', + make_exec_logging_callback(cb)); } function test_commonjs(cb) { - exec('cd commonjs_out && JASMINE_CONFIG_PATH=jasmine.json NODE_PATH=test_node_modules ../node_modules/.bin/jasmine', - make_exec_logging_callback(cb)); + exec('cd commonjs_out && JASMINE_CONFIG_PATH=jasmine.json NODE_PATH=test_node_modules ../node_modules/.bin/jasmine --random=false', + make_exec_logging_callback(cb)); } function remove_gen_files(cb) { - exec('rm -rf commonjs_out google-protobuf.js deps.js', + exec('rm -rf commonjs_out google-protobuf.js deps.js google-protobuf-*.tgz conformance/protos/*.js', make_exec_logging_callback(cb)); } exports.build_protoc_plugin = function (cb) { exec('bazel build generator:protoc-gen-js', - make_exec_logging_callback(cb)); + make_exec_logging_callback(cb)); } const dist = series(exports.build_protoc_plugin, - genproto_wellknowntypes, - gen_google_protobuf_js); + genproto_wellknowntypes, + gen_google_protobuf_js); exports.dist = series(enableAdvancedOptimizations, dist); exports.build_commonjs = series( - dist, - genproto_well_known_types_commonjs, - genproto_group1_commonjs, genproto_group2_commonjs, - genproto_commonjs_wellknowntypes, - commonjs_testdeps, genproto_group3_commonjs_strict, - commonjs_out); + dist, + genproto_well_known_types_commonjs, + genproto_group1_commonjs, genproto_group2_commonjs, + genproto_commonjs_wellknowntypes, + commonjs_testdeps, genproto_group3_commonjs_strict, + commonjs_out); exports.build_closure = series(exports.build_protoc_plugin, - genproto_well_known_types_closure, - genproto_group1_closure, - genproto_group2_closure, - closure_make_deps); + genproto_well_known_types_closure, + genproto_group1_closure, + genproto_group2_closure, + closure_make_deps); const test_closure_series = series( - exports.build_closure, - test_closure); + exports.build_closure, + test_closure); exports.test_closure = series(enableSimpleOptimizations, - test_closure_series); + test_closure_series); exports.test_closure_opt = series(enableAdvancedOptimizations, - test_closure_series); - + test_closure_series); const test_commonjs_series = series( - exports.build_commonjs, - test_commonjs); + exports.build_commonjs, + test_commonjs); exports.test_commonjs = series(enableSimpleOptimizations, - test_commonjs_series); + test_commonjs_series); exports.test_commonjs_opt = series(enableAdvancedOptimizations, - test_commonjs_series); + test_commonjs_series); const test_series = series(test_closure_series, - test_commonjs_series); + test_commonjs_series); exports.test = series(enableSimpleOptimizations, - test_series); + test_series); exports.test_opt = series(enableAdvancedOptimizations, - test_series); + test_series); + +exports.test_conformance = series(dist, genproto_conformance_commonjs, pack_google_protobuf, install_conformance_test_deps, run_conformance_tests); exports.clean = series(remove_gen_files); diff --git a/internal_bytes.js b/internal_bytes.js new file mode 100644 index 0000000..87b3f2b --- /dev/null +++ b/internal_bytes.js @@ -0,0 +1,238 @@ +/** + * @fileoverview Internal runtime functions related to bytes data. + */ +goog.module('jspb.internal_bytes'); + +const base64 = goog.require('goog.crypt.base64'); +const userAgent = goog.require('goog.userAgent'); +const {fail} = goog.require('goog.asserts'); + +/** + * Does this JavaScript environment support Uint8Array typed arrays? + * + * NOTE: this really should be `typeof Uint8Array === 'function'` but in + * go/cobalt browsers `typeof Uint8Array === 'object'` so we need a looser check + * here. + * We assume availability if featureset year is >=2018. Any number > 2012 would + * probably suffice but those years aren't defined, so we pick the ealiest + * defined year. + * + * @const {boolean} + */ +const SUPPORTS_UINT8ARRAY = + goog.FEATURESET_YEAR >= 2018 || (typeof Uint8Array !== 'undefined'); + +/** + * @define {boolean} Indicates that we should look for WEBSAFE encodings before + * using atob/btoa for base64 encoding. This incurs some walltime cost but + * is required for backcompat. + */ +const HANDLE_WEB_SAFE_ENCODINGS_WITH_ATOB_AND_BTOA = + goog.define('jspb.HANDLE_WEB_SAFE_ENCODINGS_WITH_ATOB_AND_BTOA', true); + +/** + * @define {boolean} If set, use atob/btoa when available. + * + * More specifically, if enabled and 'goog.FEATURESET_YEAR >= 2018', sets + * USE_ATOB_BTOA without feature detection. Using featureset years >= 2018 + * which is conservative but excluded IE and no one is picking numbers less that + * that anyway. If disabled, the slower JS implementation is always used; this + * is appropriate for non-Web environments where 'atob' and 'btoa' are not + * available. + */ +const CAN_USE_ATOB_AND_BTOA = goog.define('jspb.USE_ATOB_AND_BTOA', true); + +const /** boolean */ ASSUME_ATOB_AND_BTOA_AVAILABLE = + goog.FEATURESET_YEAR >= 2018; + +/** + * Whether this application has native support for `atob` and `btoa`. + * + * IE has a broken implementation; and we would be able to fast-track WEBKIT + * if it weren't for cobalt. + * + * @const {boolean} + */ +const USE_ATOB_BTOA = CAN_USE_ATOB_AND_BTOA && + (ASSUME_ATOB_AND_BTOA_AVAILABLE || + (!userAgent.IE && typeof btoa === 'function')); + +/** + * Maximum arg spread for String.fromCharCode. + * + * Chrome's maximum stack size is of the order of 100k so here we conservatively + * set this to 10k. + */ +const UINT8ARRAY_MAX_SIZE_FOR_SPREAD = 10240; + +/** + * Encodes a Uint8Array as base64. + * + * Note that we can always use btoa/atob if we can use Uint8Array because + * every browser that supports Uint8Array also supports them: see + * https://caniuse.com/?search=Uint8Array and https://caniuse.com/atob-btoa + * + * @param {!Uint8Array} u8 + * @return {string} + */ +function encodeByteArray(u8) { + if (!USE_ATOB_BTOA) { + return base64.encodeByteArray(u8); + } + + /** @type {string} */ + let binary = ''; + let offset = 0; + const limit = u8.length - UINT8ARRAY_MAX_SIZE_FOR_SPREAD; + while (offset < limit) { + binary += String.fromCharCode.apply( + null, u8.subarray(offset, offset += UINT8ARRAY_MAX_SIZE_FOR_SPREAD)); + } + binary += String.fromCharCode.apply(null, offset ? u8.subarray(offset) : u8); + return btoa(binary); +} + +/** + * Websafe padding characters for replacement. + * @const {!RegExp} + */ +const WEBSAFE_BASE64_CHARS = /** @pureOrBreakMyCode */ (/[-_.]/g); + +/** @const {!Object} */ +const websafeReplacer = + /** @pureOrBreakMyCode */ ({'-': '+', '_': '/', '.': '='}); + +/** + * Replaces websafe characters with default alphabet characters. + * @param {string} char + * @return {string} + */ +function replaceWebsafe(char) { + return websafeReplacer[char] || ''; +} + +/** + * Replaces websafe characters in a string with default alphabet characters. + * @param {string} str + * @return {string} + */ +function replaceWebsafeString(str) { + if (WEBSAFE_BASE64_CHARS.test(str)) { + return str.replace(WEBSAFE_BASE64_CHARS, replaceWebsafe); + } + return str; +} + +/** + * Decodes base64 into a Uint8Array. + * + * Note that we can always use btoa/atob if we can use Uint8Array because + * every browser that supports Uint8Array also supports them: see + * https://caniuse.com/?search=Uint8Array and https://caniuse.com/atob-btoa + * + * @param {string} b64 + * @return {!Uint8Array} + */ +function decodeByteArray(b64) { + // Without our flag, fall back to Closure's implementation. + if (!USE_ATOB_BTOA) { + return base64.decodeStringToUint8Array(b64); + } + + // If this encoding used the websafe alphabet, we must convert it for atob + // to work. This is rare so we condition any replace operation on a regex + // match. One also presumes this will happen exclusively for URL mappings + // so they should also be relatively short. + // + // Note that atob natively handles missing padding so we do not need to + // handle that here. + let encoded = b64; + if (HANDLE_WEB_SAFE_ENCODINGS_WITH_ATOB_AND_BTOA) { + encoded = replaceWebsafeString(encoded); + } + + // Convert b64 to binary string. + let /** string|undefined */ binary; + if (goog.DEBUG) { + try { + binary = atob(encoded); + } catch (e) { + throw new Error(`invalid encoding '${b64}': ${e}`); + } + } else /* if (!goog.DEBUG) */ { + binary = atob(encoded); + } + + // Convert back from binary string to Uint8Array. + const u8 = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) { + u8[i] = binary.charCodeAt(i); + } + return u8; +} + + +/** + * Coerce data of 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. + */ +function dataAsU8(value) { + if (value == null || isU8(value)) { + return /** @type {?Uint8Array} */ (value); + } + if (typeof value === 'string') { + return decodeByteArray(value); + } + fail('Cannot coerce to Uint8Array: ' + goog.typeOf(value)); + return null; +} + +/** + * Returns whether the given value is a Uint8Array. + * @param {*} value + * @return {boolean} + */ +function isU8(value) { + return SUPPORTS_UINT8ARRAY && value != null && value instanceof Uint8Array; +} + +/** @return {boolean} */ +function uint8ArrayEquals(/** !Uint8Array */ a, /** !Uint8Array */ b) { + // Compare byte-by-byte. + // + // Note that we do not technically need to length-check since out-of-range + // subscripts will simply yield `undefined`; but that may result in some + // deoptimization. + const aLength = a.length; + if (aLength !== b.length) { + return false; + } + for (let i = 0; i < aLength; i++) { + if (a[i] !== b[i]) { + return false; + } + } + return true; +} + +/** + * A token to check that internal only bytestring functions are only called by + * internal functions. + * @const + */ +const I_AM_INTERNAL = {}; + +exports = { + I_AM_INTERNAL, + SUPPORTS_UINT8ARRAY, + encodeByteArray, + decodeByteArray, + dataAsU8, + isU8, + replaceWebsafeString, + uint8ArrayEquals, + USE_ATOB_BTOA, // exported for tests. +}; diff --git a/internal_options.js b/internal_options.js new file mode 100644 index 0000000..ecdac9e --- /dev/null +++ b/internal_options.js @@ -0,0 +1,46 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://protobuf.dev/ +// +// 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 Internal options for. + */ +goog.module('jspb.internal_options'); + +/** + * @return {boolean} True if BigInt is permitted for use and supported by the + * platform. + * @nosideeffects + */ +function isBigIntAvailable() { + return goog.FEATURESET_YEAR >= 2021 || (typeof BigInt === 'function'); +} + +exports = { + isBigIntAvailable, +}; diff --git a/internal_public.js b/internal_public.js new file mode 100644 index 0000000..cf4a61d --- /dev/null +++ b/internal_public.js @@ -0,0 +1,112 @@ +/** + * @fileoverview Public APIs exposed purely for use by generated code. Use of + * these APIs outside of that context is not supported and actively discouraged. + * @public + * + * DO NOT USE THIS OUTSIDE OF THIS PACKAGE. + */ + +goog.module('jspb.internal.public_for_gencode'); +goog.module.declareLegacyNamespace(); + +const asserts = goog.require('goog.asserts'); +const { BinaryReader } = goog.require('jspb.binary.reader'); +const { BinaryWriter } = goog.requireType('jspb.binary.writer'); +const {Map: JspbMap} = goog.requireType('jspb.Map'); + +/** + * Write this Map field in wire format to a BinaryWriter, using the given + * field number. + * @param {?JspbMap} map + * @param {number} fieldNumber + * @param {!BinaryWriter} writer + * @param {function(this:BinaryWriter,number,K_OR_NULL)} keyWriterFn + * The method on BinaryWriter that writes type K to the stream. + * @param {function(this:BinaryWriter,number,V,?=)| + * function(this:BinaryWriter,number,V,?)} valueWriterFn + * The method on BinaryWriter that writes type V to the stream. May be + * writeMessage, in which case the second callback arg form is used. + * @param {function(V,!BinaryWriter)=} valueWriterCallback + * The BinaryWriter serialization callback for type V, if V is a message + * type. + * @template K,V + * Use go/closure-ttl to create a `K|null` type for the keyWriterFn argument + * closure type inference will occasionally infer K based on the keyWriterFn + * argument instead of the map argument which will cause type errors when they + * don't match + * @template K_OR_NULL := union(K, 'null') =: + */ +function serializeMapToBinary( + map, fieldNumber, writer, keyWriterFn, valueWriterFn, valueWriterCallback) { + if (!map) { + return; + } + map.forEach((value, key) => { + writer.writeMessage( + fieldNumber, /* we need a non-null value to pass here */ map, + (ignored, w) => { + keyWriterFn.call(w, 1, key); + valueWriterFn.call(w, 2, value, valueWriterCallback); + }); + }); +} + +/** + * Read one key/value message from the given BinaryReader. Compatible as the + * `reader` callback parameter to BinaryReader.readMessage, to be called + * when a key/value pair submessage is encountered. If the Key is undefined, + * we should default it to 0. + * @template K, V + * @param {!JspbMap} map + * @param {!BinaryReader} reader + * @param {function(this:BinaryReader):K} keyReaderFn + * The method on BinaryReader that reads type K from the stream. + * + * @param {K} 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 + * + * @param {function(this:BinaryReader):V|function(V,!BinaryReader)} + * valueReaderFn + * The method on BinaryReader that reads type V from the stream, or a + * callback for readMessage. + * + * @param {V} 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 + */ +function deserializeMapFromBinary( + map, reader, keyReaderFn, defaultKey, valueReaderFn, defaultValue) { + reader.readMessage(map, (message, reader) => { + let key = defaultKey; + let value = defaultValue; + + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + const field = reader.getFieldNumber(); + + if (field == 1) { + // Key. + key = keyReaderFn.call(reader); + } else if (field == 2) { + // Value. + if (map.valueCtor) { + reader.readMessage(value, valueReaderFn); + } else { + value = (/** @type {function(this:BinaryReader):?} */ (valueReaderFn)) + .call(reader); + } + } + } + + asserts.assert(key != undefined); + asserts.assert(value != undefined); + map.set(key, value); + }); +} + +exports = {deserializeMapFromBinary, serializeMapToBinary}; diff --git a/map.js b/map.js index 9bb6eb5..e917941 100644 --- a/map.js +++ b/map.js @@ -31,10 +31,10 @@ /** * @fileoverview - * @suppress {missingRequire} TODO(b/152540451): this shouldn't be needed */ goog.provide('jspb.Map'); + goog.require('jspb.asserts'); goog.requireType('jspb.BinaryReader'); @@ -425,43 +425,6 @@ jspb.Map.prototype.has = function(key) { return (keyValue in this.map_); }; - -/** - * Write this Map field in wire format to a BinaryWriter, using the given field - * number. - * @param {number} fieldNumber - * @param {!jspb.BinaryWriter} writer - * @param {function(this:jspb.BinaryWriter,number,K)} keyWriterFn - * The method on BinaryWriter that writes type K to the stream. - * @param {function(this:jspb.BinaryWriter,number,V,?=)| - * function(this:jspb.BinaryWriter,number,V,?)} valueWriterFn - * The method on BinaryWriter that writes type V to the stream. May be - * writeMessage, in which case the second callback arg form is used. - * @param {function(V,!jspb.BinaryWriter)=} opt_valueWriterCallback - * The BinaryWriter serialization callback for type V, if V is a message - * type. - * @export - */ -jspb.Map.prototype.serializeBinary = function( - fieldNumber, writer, keyWriterFn, valueWriterFn, opt_valueWriterCallback) { - var strKeys = this.stringKeys_(); - strKeys.sort(); - for (var i = 0; i < strKeys.length; i++) { - var entry = this.map_[strKeys[i]]; - writer.beginSubMessage(fieldNumber); - keyWriterFn.call(writer, 1, entry.key); - if (this.valueCtor_) { - valueWriterFn.call(writer, 2, this.wrapEntry_(entry), - opt_valueWriterCallback); - } else { - /** @type {function(this:jspb.BinaryWriter,number,?)} */ (valueWriterFn) - .call(writer, 2, entry.value); - } - writer.endSubMessage(); - } -}; - - /** * Read one key/value message from the given BinaryReader. Compatible as the * `reader` callback parameter to jspb.BinaryReader.readMessage, to be called diff --git a/message.js b/message.js index a13d856..f4db81a 100644 --- a/message.js +++ b/message.js @@ -43,7 +43,7 @@ goog.require('goog.array'); goog.require('goog.crypt.base64'); goog.require('jspb.asserts'); -goog.require('jspb.BinaryReader'); +goog.require('jspb.binary.reader'); goog.require('jspb.Map'); @@ -657,7 +657,14 @@ jspb.Message.readBinaryExtension = function( reader, value, binaryFieldInfo.binaryMessageDeserializeFn); } else { // All other types. - value = binaryFieldInfo.binaryReaderFn.call(reader); + if (fieldInfo.isRepeated && binaryFieldInfo.isPacked) { + value = getExtensionFn.call(msg, fieldInfo) ?? []; + binaryFieldInfo.binaryReaderFn.call(reader, value); + setExtensionFn.call(msg, fieldInfo, value); + return; + } else { + value = binaryFieldInfo.binaryReaderFn.call(reader); + } } if (fieldInfo.isRepeated && !binaryFieldInfo.isPacked) { diff --git a/node_loader.js b/node_loader.js index 3f25e92..82e5a68 100644 --- a/node_loader.js +++ b/node_loader.js @@ -32,7 +32,11 @@ * @fileoverview Loader that handles goog.require() for Node.JS. */ -var oldLoader = goog.global.CLOSURE_IMPORT_SCRIPT; +const fs = require('fs'); +const path = require('path'); + +// For goog.require() +const OLD_CLOSURE_IMPORT_SCRIPT = goog.global.CLOSURE_IMPORT_SCRIPT; goog.global.CLOSURE_IMPORT_SCRIPT = function(src, opt_sourceText) { if (opt_sourceText === undefined) { @@ -45,5 +49,15 @@ goog.global.CLOSURE_IMPORT_SCRIPT = function(src, opt_sourceText) { } } - return oldLoader(src, opt_sourceText); + return OLD_CLOSURE_IMPORT_SCRIPT(src, opt_sourceText); +}; + +const OLD_CLOSURE_LOAD_FILE_SYNC = goog.global.CLOSURE_LOAD_FILE_SYNC; + +goog.global.CLOSURE_LOAD_FILE_SYNC = function (src) { + try { + return fs.readFileSync(path.resolve('.', src), { encoding: 'utf-8' }); + } catch (e) { + return OLD_CLOSURE_LOAD_FILE_SYNC(src); + } }; diff --git a/package-lock.json b/package-lock.json index 4fe22bb..088cd4a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,18 @@ { "name": "google-protobuf", - "version": "3.21.4", + "version": "3.21.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "google-protobuf", - "version": "3.21.4", + "version": "3.21.5", "license": "(BSD-3-Clause AND Apache-2.0)", "devDependencies": { "glob": "~7.1.4", - "google-closure-compiler": "~20190819.0.0", - "google-closure-deps": "^20210406.0.0", - "google-closure-library": "~20200315.0.0", + "google-closure-compiler": "20230802.0.0", + "google-closure-deps": "20230802.0.0", + "google-closure-library": "20230802.0.0", "gulp": "~5.0.0", "jasmine": "~3.5.0" } @@ -48,15 +48,19 @@ } }, "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^1.9.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/anymatch": { @@ -253,17 +257,20 @@ } }, "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/chokidar": { @@ -337,19 +344,24 @@ } }, "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "license": "MIT", "dependencies": { - "color-name": "1.1.3" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" }, "node_modules/concat-map": { "version": "0.0.1", @@ -428,15 +440,6 @@ "node": ">=6" } }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/expand-tilde": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", @@ -731,14 +734,14 @@ } }, "node_modules/google-closure-compiler": { - "version": "20190819.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler/-/google-closure-compiler-20190819.0.0.tgz", - "integrity": "sha512-5cwcui89TSFrKCa+oH5zq6F0esSHkGbWt1U2LgywakFhe5F27j/0F6NR7QyFYkBf3leDt5kPBvC2Dlc3ZwuFVA==", + "version": "20230802.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler/-/google-closure-compiler-20230802.0.0.tgz", + "integrity": "sha512-o2fYoc8lqOBdhm95Ick0vWrtwH2Icd5yLZhbTcQ0T7NfGiBepYvx1BB63hR8ebgzEZemz9Fh+O6Kg/3Mjm28ww==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "chalk": "2.x", - "google-closure-compiler-java": "^20190819.0.0", - "google-closure-compiler-js": "^20190819.0.0", + "chalk": "4.x", + "google-closure-compiler-java": "^20230802.0.0", "minimist": "1.x", "vinyl": "2.x", "vinyl-sourcemaps-apply": "^0.2.0" @@ -747,73 +750,73 @@ "google-closure-compiler": "cli.js" }, "engines": { - "node": ">=8" + "node": ">=10" }, "optionalDependencies": { - "google-closure-compiler-linux": "^20190819.0.0", - "google-closure-compiler-osx": "^20190819.0.0", - "google-closure-compiler-windows": "^20190819.0.0" + "google-closure-compiler-linux": "^20230802.0.0", + "google-closure-compiler-osx": "^20230802.0.0", + "google-closure-compiler-windows": "^20230802.0.0" } }, "node_modules/google-closure-compiler-java": { - "version": "20190819.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-java/-/google-closure-compiler-java-20190819.0.0.tgz", - "integrity": "sha512-i8KzPJZBEF0aZWSn2vlkH0JCeQGIxBVfQaUwc2fCjPbm/v5YfpZzkj38LoMDbE95BlYsq2cPveHUNC4w85UgGw==", - "dev": true - }, - "node_modules/google-closure-compiler-js": { - "version": "20190819.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-js/-/google-closure-compiler-js-20190819.0.0.tgz", - "integrity": "sha512-c1MrCW2sBsJ5d5judH6YnBPpxbOzts6D7XYa0KN4I97S0LjXzX5cENBlMUqMGGeSdFyjGr5FP0BxGp49k5UKxQ==", - "dev": true + "version": "20230802.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-java/-/google-closure-compiler-java-20230802.0.0.tgz", + "integrity": "sha512-PWKLMLwj7pR/U0yYbiy649LLqAscu+F1gyY4Y/jK6CmSLb8cIJbL8BTJd00828TzTNfWnYwxbkcQw0y9C2YsGw==", + "dev": true, + "license": "Apache-2.0" }, "node_modules/google-closure-compiler-linux": { - "version": "20190819.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-linux/-/google-closure-compiler-linux-20190819.0.0.tgz", - "integrity": "sha512-+ia89Ot4dPyRioLlxvkfYiISwJO+2NV7SY0Zp/KfTt9x+eJaGQ9GRIQage50d1mGkUyPmFfxsdPUePpT1QlpaA==", + "version": "20230802.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-linux/-/google-closure-compiler-linux-20230802.0.0.tgz", + "integrity": "sha512-F13U4iSXiWeGtHOFS25LVem1s6zI+pJvXVPVR7zSib5ppoUJ0JXnABJQezUR3FnpxmnkALG4oIGW0syH9zPLZA==", "cpu": [ - "x64", - "x86" + "x32", + "x64" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "linux" ] }, "node_modules/google-closure-compiler-osx": { - "version": "20190819.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-osx/-/google-closure-compiler-osx-20190819.0.0.tgz", - "integrity": "sha512-woANg+oA2zjFebtdYTA4lLtqbOVO9yE9yy8ibGjt76FNlw0bAyxTO4z2ab4CwUNNpOeRoxZyGt9CejlCmNpxJg==", + "version": "20230802.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-osx/-/google-closure-compiler-osx-20230802.0.0.tgz", + "integrity": "sha512-ANAi/ux92Tt+Na7vFDLeK2hRzotjC5j+nxoPtE0OcuNcbjji5dREKoJxkq7r0YwRTCzAFZszK5ip/NPdTOdCEg==", "cpu": [ + "x32", "x64", - "x86" + "arm64" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "darwin" ] }, "node_modules/google-closure-compiler-windows": { - "version": "20190819.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-windows/-/google-closure-compiler-windows-20190819.0.0.tgz", - "integrity": "sha512-WglPY706c1oN03KiZF02FUWr/7M0eFoev4CQPncSihfjYXpD5E5PMKJ3ppawhzj60CQ9ceC/Omx7VN7Kp0uOQg==", + "version": "20230802.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-windows/-/google-closure-compiler-windows-20230802.0.0.tgz", + "integrity": "sha512-ZQPujoNiiUyTGl8zEGR/0yAygWnbMtX/NQ/S/EHVgq5nmYkvDEVuiVbgpPAmO9lzBTq0hvUTRRATZbTU2ISxgA==", "cpu": [ - "x64", - "x86" + "x32", + "x64" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "win32" ] }, "node_modules/google-closure-deps": { - "version": "20210406.0.0", - "resolved": "https://registry.npmjs.org/google-closure-deps/-/google-closure-deps-20210406.0.0.tgz", - "integrity": "sha512-4mn6qZ8u4c/9fhebKccxyN882l5/0O4nuJ+ibuxDy0y7XMgolSLNF/Gmg1HEhEgX00CF/JBKrc/rw0WVjnlSfw==", + "version": "20230802.0.0", + "resolved": "https://registry.npmjs.org/google-closure-deps/-/google-closure-deps-20230802.0.0.tgz", + "integrity": "sha512-5xhXO8xgxdXYZMsc8ZRzmnCm4prm0TcD33tcNr8l3OjSBovFO9MpgtrZxH/ZVnZMny6oOBmdeNqcO3afCYY9Lw==", "dev": true, + "license": "Apache-2.0", "dependencies": { "minimatch": "^3.0.4", "yargs": "^16.2.0" @@ -824,10 +827,11 @@ } }, "node_modules/google-closure-library": { - "version": "20200315.0.0", - "resolved": "https://registry.npmjs.org/google-closure-library/-/google-closure-library-20200315.0.0.tgz", - "integrity": "sha512-LdCecdztxDvA9K+8b6E9aE4Hw7ABdYokfA7d5qW1sd/iy8O1l0+eXLlddE2JtW62gf8UjcPskBua4fU4J5lmHA==", - "dev": true + "version": "20230802.0.0", + "resolved": "https://registry.npmjs.org/google-closure-library/-/google-closure-library-20230802.0.0.tgz", + "integrity": "sha512-h2lBEX2tjWwfR+dhFukGER15vKY+4cA3nRqwKSOsBdyizlOxTAHOWbEYiGnMoom3/SrVeQ5uG0eKsbxQ3wbsxA==", + "dev": true, + "license": "Apache-2.0" }, "node_modules/graceful-fs": { "version": "4.2.11", @@ -879,76 +883,6 @@ "node": ">=10.13.0" } }, - "node_modules/gulp-cli/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/gulp-cli/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/gulp-cli/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/gulp-cli/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/gulp-cli/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/gulp-cli/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/gulplog": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-2.2.0.tgz", @@ -962,12 +896,13 @@ } }, "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/hasown": { @@ -1699,15 +1634,16 @@ } }, "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/supports-preserve-symlinks-flag": { @@ -2015,39 +1951,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -2114,12 +2017,12 @@ "dev": true }, "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "color-convert": "^2.0.1" } }, "anymatch": { @@ -2260,14 +2163,13 @@ } }, "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, "chokidar": { @@ -2327,18 +2229,18 @@ } }, "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "color-name": "1.1.3" + "color-name": "~1.1.4" } }, "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, "concat-map": { @@ -2406,12 +2308,6 @@ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "dev": true }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, "expand-tilde": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", @@ -2641,59 +2537,52 @@ } }, "google-closure-compiler": { - "version": "20190819.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler/-/google-closure-compiler-20190819.0.0.tgz", - "integrity": "sha512-5cwcui89TSFrKCa+oH5zq6F0esSHkGbWt1U2LgywakFhe5F27j/0F6NR7QyFYkBf3leDt5kPBvC2Dlc3ZwuFVA==", + "version": "20230802.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler/-/google-closure-compiler-20230802.0.0.tgz", + "integrity": "sha512-o2fYoc8lqOBdhm95Ick0vWrtwH2Icd5yLZhbTcQ0T7NfGiBepYvx1BB63hR8ebgzEZemz9Fh+O6Kg/3Mjm28ww==", "dev": true, "requires": { - "chalk": "2.x", - "google-closure-compiler-java": "^20190819.0.0", - "google-closure-compiler-js": "^20190819.0.0", - "google-closure-compiler-linux": "^20190819.0.0", - "google-closure-compiler-osx": "^20190819.0.0", - "google-closure-compiler-windows": "^20190819.0.0", + "chalk": "4.x", + "google-closure-compiler-java": "^20230802.0.0", + "google-closure-compiler-linux": "^20230802.0.0", + "google-closure-compiler-osx": "^20230802.0.0", + "google-closure-compiler-windows": "^20230802.0.0", "minimist": "1.x", "vinyl": "2.x", "vinyl-sourcemaps-apply": "^0.2.0" } }, "google-closure-compiler-java": { - "version": "20190819.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-java/-/google-closure-compiler-java-20190819.0.0.tgz", - "integrity": "sha512-i8KzPJZBEF0aZWSn2vlkH0JCeQGIxBVfQaUwc2fCjPbm/v5YfpZzkj38LoMDbE95BlYsq2cPveHUNC4w85UgGw==", - "dev": true - }, - "google-closure-compiler-js": { - "version": "20190819.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-js/-/google-closure-compiler-js-20190819.0.0.tgz", - "integrity": "sha512-c1MrCW2sBsJ5d5judH6YnBPpxbOzts6D7XYa0KN4I97S0LjXzX5cENBlMUqMGGeSdFyjGr5FP0BxGp49k5UKxQ==", + "version": "20230802.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-java/-/google-closure-compiler-java-20230802.0.0.tgz", + "integrity": "sha512-PWKLMLwj7pR/U0yYbiy649LLqAscu+F1gyY4Y/jK6CmSLb8cIJbL8BTJd00828TzTNfWnYwxbkcQw0y9C2YsGw==", "dev": true }, "google-closure-compiler-linux": { - "version": "20190819.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-linux/-/google-closure-compiler-linux-20190819.0.0.tgz", - "integrity": "sha512-+ia89Ot4dPyRioLlxvkfYiISwJO+2NV7SY0Zp/KfTt9x+eJaGQ9GRIQage50d1mGkUyPmFfxsdPUePpT1QlpaA==", + "version": "20230802.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-linux/-/google-closure-compiler-linux-20230802.0.0.tgz", + "integrity": "sha512-F13U4iSXiWeGtHOFS25LVem1s6zI+pJvXVPVR7zSib5ppoUJ0JXnABJQezUR3FnpxmnkALG4oIGW0syH9zPLZA==", "dev": true, "optional": true }, "google-closure-compiler-osx": { - "version": "20190819.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-osx/-/google-closure-compiler-osx-20190819.0.0.tgz", - "integrity": "sha512-woANg+oA2zjFebtdYTA4lLtqbOVO9yE9yy8ibGjt76FNlw0bAyxTO4z2ab4CwUNNpOeRoxZyGt9CejlCmNpxJg==", + "version": "20230802.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-osx/-/google-closure-compiler-osx-20230802.0.0.tgz", + "integrity": "sha512-ANAi/ux92Tt+Na7vFDLeK2hRzotjC5j+nxoPtE0OcuNcbjji5dREKoJxkq7r0YwRTCzAFZszK5ip/NPdTOdCEg==", "dev": true, "optional": true }, "google-closure-compiler-windows": { - "version": "20190819.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-windows/-/google-closure-compiler-windows-20190819.0.0.tgz", - "integrity": "sha512-WglPY706c1oN03KiZF02FUWr/7M0eFoev4CQPncSihfjYXpD5E5PMKJ3ppawhzj60CQ9ceC/Omx7VN7Kp0uOQg==", + "version": "20230802.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-windows/-/google-closure-compiler-windows-20230802.0.0.tgz", + "integrity": "sha512-ZQPujoNiiUyTGl8zEGR/0yAygWnbMtX/NQ/S/EHVgq5nmYkvDEVuiVbgpPAmO9lzBTq0hvUTRRATZbTU2ISxgA==", "dev": true, "optional": true }, "google-closure-deps": { - "version": "20210406.0.0", - "resolved": "https://registry.npmjs.org/google-closure-deps/-/google-closure-deps-20210406.0.0.tgz", - "integrity": "sha512-4mn6qZ8u4c/9fhebKccxyN882l5/0O4nuJ+ibuxDy0y7XMgolSLNF/Gmg1HEhEgX00CF/JBKrc/rw0WVjnlSfw==", + "version": "20230802.0.0", + "resolved": "https://registry.npmjs.org/google-closure-deps/-/google-closure-deps-20230802.0.0.tgz", + "integrity": "sha512-5xhXO8xgxdXYZMsc8ZRzmnCm4prm0TcD33tcNr8l3OjSBovFO9MpgtrZxH/ZVnZMny6oOBmdeNqcO3afCYY9Lw==", "dev": true, "requires": { "minimatch": "^3.0.4", @@ -2701,9 +2590,9 @@ } }, "google-closure-library": { - "version": "20200315.0.0", - "resolved": "https://registry.npmjs.org/google-closure-library/-/google-closure-library-20200315.0.0.tgz", - "integrity": "sha512-LdCecdztxDvA9K+8b6E9aE4Hw7ABdYokfA7d5qW1sd/iy8O1l0+eXLlddE2JtW62gf8UjcPskBua4fU4J5lmHA==", + "version": "20230802.0.0", + "resolved": "https://registry.npmjs.org/google-closure-library/-/google-closure-library-20230802.0.0.tgz", + "integrity": "sha512-h2lBEX2tjWwfR+dhFukGER15vKY+4cA3nRqwKSOsBdyizlOxTAHOWbEYiGnMoom3/SrVeQ5uG0eKsbxQ3wbsxA==", "dev": true }, "graceful-fs": { @@ -2742,57 +2631,6 @@ "string-width": "^4.2.3", "v8flags": "^4.0.0", "yargs": "^16.2.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } } }, "gulplog": { @@ -2805,9 +2643,9 @@ } }, "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, "hasown": { @@ -3363,12 +3201,12 @@ } }, "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" } }, "supports-preserve-symlinks-flag": { @@ -3614,32 +3452,6 @@ "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - } } }, "wrappy": { diff --git a/package.json b/package.json index 277fed4..c35f530 100644 --- a/package.json +++ b/package.json @@ -12,12 +12,11 @@ "package.json", "README.md" ], - "dependencies": {}, "devDependencies": { "glob": "~7.1.4", - "google-closure-compiler": "~20190819.0.0", - "google-closure-deps": "^20210406.0.0", - "google-closure-library": "~20200315.0.0", + "google-closure-compiler": "20230802.0.0", + "google-closure-deps": "20230802.0.0", + "google-closure-library": "20230802.0.0", "gulp": "~5.0.0", "jasmine": "~3.5.0" }, diff --git a/proto3_test.js b/proto3_test.js index c1b3a44..5c0af41 100644 --- a/proto3_test.js +++ b/proto3_test.js @@ -225,15 +225,15 @@ describe('proto3Test', () => { let msg = new proto.jspb.test.TestProto3(); msg.setSingularInt32(-42); - msg.setSingularInt64(-0x7fffffff00000000); + msg.setSingularInt64(String(-BigInt('0x7fffffff00000000'))); msg.setSingularUint32(0x80000000); - msg.setSingularUint64(0xf000000000000000); + msg.setSingularUint64(String(BigInt('0xf000000000000000'))); msg.setSingularSint32(-100); - msg.setSingularSint64(-0x8000000000000000); + msg.setSingularSint64(String(-BigInt('0x8000000000000000'))); msg.setSingularFixed32(1234); - msg.setSingularFixed64(0x1234567800000000); + msg.setSingularFixed64(String(BigInt('0x1234567800000000'))); msg.setSingularSfixed32(-1234); - msg.setSingularSfixed64(-0x1234567800000000); + msg.setSingularSfixed64(String(-BigInt('0x1234567800000000'))); msg.setSingularFloat(1.5); msg.setSingularDouble(-1.5); msg.setSingularBool(true); @@ -245,15 +245,15 @@ describe('proto3Test', () => { msg.setSingularForeignEnum(proto.jspb.test.Proto3Enum.PROTO3_BAR); msg.setRepeatedInt32List([-42]); - msg.setRepeatedInt64List([-0x7fffffff00000000]); + msg.setRepeatedInt64List([String(-BigInt('0x7fffffff00000000'))]); msg.setRepeatedUint32List([0x80000000]); - msg.setRepeatedUint64List([0xf000000000000000]); + msg.setRepeatedUint64List([String(BigInt('0xf000000000000000'))]); msg.setRepeatedSint32List([-100]); - msg.setRepeatedSint64List([-0x8000000000000000]); + msg.setRepeatedSint64List([String(-BigInt('0x8000000000000000'))]); msg.setRepeatedFixed32List([1234]); - msg.setRepeatedFixed64List([0x1234567800000000]); + msg.setRepeatedFixed64List([String(BigInt('0x1234567800000000'))]); msg.setRepeatedSfixed32List([-1234]); - msg.setRepeatedSfixed64List([-0x1234567800000000]); + msg.setRepeatedSfixed64List([String(-BigInt('0x1234567800000000'))]); msg.setRepeatedFloatList([1.5]); msg.setRepeatedDoubleList([-1.5]); msg.setRepeatedBoolList([true]); @@ -270,15 +270,15 @@ describe('proto3Test', () => { msg = proto.jspb.test.TestProto3.deserializeBinary(serialized); expect(msg.getSingularInt32()).toEqual(-42); - expect(msg.getSingularInt64()).toEqual(-0x7fffffff00000000); + expect(msg.getSingularInt64()).toEqual(String(-BigInt('0x7fffffff00000000'))); expect(msg.getSingularUint32()).toEqual(0x80000000); - expect(msg.getSingularUint64()).toEqual(0xf000000000000000); + expect(msg.getSingularUint64()).toEqual(String(BigInt('0xf000000000000000'))); expect(msg.getSingularSint32()).toEqual(-100); - expect(msg.getSingularSint64()).toEqual(-0x8000000000000000); + expect(msg.getSingularSint64()).toEqual(String(-BigInt('0x8000000000000000'))); expect(msg.getSingularFixed32()).toEqual(1234); - expect(msg.getSingularFixed64()).toEqual(0x1234567800000000); + expect(msg.getSingularFixed64()).toEqual(String(BigInt('0x1234567800000000'))); expect(msg.getSingularSfixed32()).toEqual(-1234); - expect(msg.getSingularSfixed64()).toEqual(-0x1234567800000000); + expect(msg.getSingularSfixed64()).toEqual(String(-BigInt('0x1234567800000000'))); expect(msg.getSingularFloat()).toEqual(1.5); expect(msg.getSingularDouble()).toEqual(-1.5); expect(msg.getSingularBool()).toBeTrue(); @@ -289,15 +289,15 @@ describe('proto3Test', () => { .toEqual(proto.jspb.test.Proto3Enum.PROTO3_BAR); expect(msg.getRepeatedInt32List()).toEqual([-42]); - expect(msg.getRepeatedInt64List()).toEqual([-0x7fffffff00000000]); + expect(msg.getRepeatedInt64List()).toEqual([String(-BigInt('0x7fffffff00000000'))]); expect(msg.getRepeatedUint32List()).toEqual([0x80000000]); - expect(msg.getRepeatedUint64List()).toEqual([0xf000000000000000]); + expect(msg.getRepeatedUint64List()).toEqual([String(BigInt('0xf000000000000000'))]); expect(msg.getRepeatedSint32List()).toEqual([-100]); - expect(msg.getRepeatedSint64List()).toEqual([-0x8000000000000000]); + expect(msg.getRepeatedSint64List()).toEqual([String(-BigInt('0x8000000000000000'))]); expect(msg.getRepeatedFixed32List()).toEqual([1234]); - expect(msg.getRepeatedFixed64List()).toEqual([0x1234567800000000]); + expect(msg.getRepeatedFixed64List()).toEqual([String(BigInt('0x1234567800000000'))]); expect(msg.getRepeatedSfixed32List()).toEqual([-1234]); - expect(msg.getRepeatedSfixed64List()).toEqual([-0x1234567800000000]); + expect(msg.getRepeatedSfixed64List()).toEqual([String(-BigInt('0x1234567800000000'))]); expect(msg.getRepeatedFloatList()).toEqual([1.5]); expect(msg.getRepeatedDoubleList()).toEqual([-1.5]); expect(msg.getRepeatedBoolList()).toEqual([true]); diff --git a/unsafe_bytestring.js b/unsafe_bytestring.js new file mode 100644 index 0000000..d939df2 --- /dev/null +++ b/unsafe_bytestring.js @@ -0,0 +1,71 @@ +/** + * @fileoverview Provides unsafe routines for constructing and manipulating + * ByteString objects. + * + * These can be used to construct a `ByteString` from a `Uint8Array` without a + * copy or to access a `Uint8Array` from a `ByteString` without a copy. + * + * These operations are unsafe because the contract on `ByteString` is that it + * is immutable and these functions can be used to violate that contract. + * + * If the `Uint8Array` objects returned from or passed to these functions are + * mutated the results will be unpredictable. + * + * - These mutations may or may not be reflected in later reads of the + * ByteString + * - equality operations may not be consistent + * - Other protos parsed from these ByteString objects may themselves contain + * ByteString objects that contain mutable data. + * + * Because it is very unpredictable when or how these mutations might affect + * behavior far away in an application, access to these APIs is restricted. If + * you find yourself wanting to use these APIs please reach out to + * web-protos-dev@ + * + */ + +goog.module('jspb.unsafe_bytestring'); + +const {ByteString} = goog.require('jspb.bytestring'); +const {I_AM_INTERNAL} = goog.require('jspb.internal_bytes'); +const {assertInstanceof} = goog.require('goog.asserts'); + +/** + * Constructs a ByteString from the Uint8Array without a copy. + * + * @return {!ByteString} + */ +function unsafeByteStringFromUint8Array(/** !Uint8Array*/ array) { + assertInstanceof(array, Uint8Array); + return array.length == 0 ? ByteString.empty() : + new ByteString(array, I_AM_INTERNAL); +} + +/** + * Returns the Uint8Array from the ByteString without a defensive copy. + * + * Mutating the returned Uint8Array (if any) can lead to unpredictable behavior. + * + * @return {!Uint8Array} + */ +function unsafeUint8ArrayFromByteString(/** !ByteString*/ bytestring) { + assertInstanceof(bytestring, ByteString); + return bytestring.internalBytesUnsafe(I_AM_INTERNAL) || new Uint8Array(0); +} + +/** + * Returns the Uint8Array or base64 string from the bytestring with no copies. + * + * Mutating the returned Uint8Array (if any) can lead to unpredictable behavior + * @return {!Uint8Array|string} + */ +function unsafeUnwrapByteString(/** !ByteString*/ bytestring) { + assertInstanceof(bytestring, ByteString); + return bytestring.internalUnwrap(I_AM_INTERNAL); +} + +exports = { + unsafeByteStringFromUint8Array, + unsafeUint8ArrayFromByteString, + unsafeUnwrapByteString, +}; diff --git a/yarn.lock b/yarn.lock index 4f70eed..814ae3a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19,21 +19,7 @@ ansi-regex@^5.0.1: resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -ansi-styles@^4.0.0: - version "4.3.0" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -ansi-styles@^4.1.0: +ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== @@ -140,7 +126,7 @@ buffer@^6.0.3: base64-js "^1.3.1" ieee754 "^1.2.1" -chalk@^4.1.2: +chalk@^4.1.2, chalk@4.x: version "4.1.2" resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -148,15 +134,6 @@ chalk@^4.1.2: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@2.x: - version "2.4.2" - resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - chokidar@^3.5.3: version "3.6.0" resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz" @@ -205,13 +182,6 @@ cloneable-readable@^1.0.0: process-nextick-args "^2.0.0" readable-stream "^2.3.5" -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - color-convert@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" @@ -224,11 +194,6 @@ color-name@~1.1.4: resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - concat-map@0.0.1: version "0.0.1" resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" @@ -282,11 +247,6 @@ escalade@^3.1.1: resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== - expand-tilde@^2.0.0, expand-tilde@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz" @@ -466,49 +426,43 @@ glogg@^2.2.0: dependencies: sparkles "^2.1.0" -google-closure-compiler-java@^20190819.0.0: - version "20190819.0.0" - resolved "https://registry.npmjs.org/google-closure-compiler-java/-/google-closure-compiler-java-20190819.0.0.tgz" - integrity sha512-i8KzPJZBEF0aZWSn2vlkH0JCeQGIxBVfQaUwc2fCjPbm/v5YfpZzkj38LoMDbE95BlYsq2cPveHUNC4w85UgGw== - -google-closure-compiler-js@^20190819.0.0: - version "20190819.0.0" - resolved "https://registry.npmjs.org/google-closure-compiler-js/-/google-closure-compiler-js-20190819.0.0.tgz" - integrity sha512-c1MrCW2sBsJ5d5judH6YnBPpxbOzts6D7XYa0KN4I97S0LjXzX5cENBlMUqMGGeSdFyjGr5FP0BxGp49k5UKxQ== - -google-closure-compiler-linux@^20190819.0.0: - version "20190819.0.0" - resolved "https://registry.npmjs.org/google-closure-compiler-linux/-/google-closure-compiler-linux-20190819.0.0.tgz" - integrity sha512-+ia89Ot4dPyRioLlxvkfYiISwJO+2NV7SY0Zp/KfTt9x+eJaGQ9GRIQage50d1mGkUyPmFfxsdPUePpT1QlpaA== - -google-closure-compiler@~20190819.0.0: - version "20190819.0.0" - resolved "https://registry.npmjs.org/google-closure-compiler/-/google-closure-compiler-20190819.0.0.tgz" - integrity sha512-5cwcui89TSFrKCa+oH5zq6F0esSHkGbWt1U2LgywakFhe5F27j/0F6NR7QyFYkBf3leDt5kPBvC2Dlc3ZwuFVA== - dependencies: - chalk "2.x" - google-closure-compiler-java "^20190819.0.0" - google-closure-compiler-js "^20190819.0.0" +google-closure-compiler-java@^20230802.0.0: + version "20230802.0.0" + resolved "https://registry.npmjs.org/google-closure-compiler-java/-/google-closure-compiler-java-20230802.0.0.tgz" + integrity sha512-PWKLMLwj7pR/U0yYbiy649LLqAscu+F1gyY4Y/jK6CmSLb8cIJbL8BTJd00828TzTNfWnYwxbkcQw0y9C2YsGw== + +google-closure-compiler-linux@^20230802.0.0: + version "20230802.0.0" + resolved "https://registry.npmjs.org/google-closure-compiler-linux/-/google-closure-compiler-linux-20230802.0.0.tgz" + integrity sha512-F13U4iSXiWeGtHOFS25LVem1s6zI+pJvXVPVR7zSib5ppoUJ0JXnABJQezUR3FnpxmnkALG4oIGW0syH9zPLZA== + +google-closure-compiler@20230802.0.0: + version "20230802.0.0" + resolved "https://registry.npmjs.org/google-closure-compiler/-/google-closure-compiler-20230802.0.0.tgz" + integrity sha512-o2fYoc8lqOBdhm95Ick0vWrtwH2Icd5yLZhbTcQ0T7NfGiBepYvx1BB63hR8ebgzEZemz9Fh+O6Kg/3Mjm28ww== + dependencies: + chalk "4.x" + google-closure-compiler-java "^20230802.0.0" minimist "1.x" vinyl "2.x" vinyl-sourcemaps-apply "^0.2.0" optionalDependencies: - google-closure-compiler-linux "^20190819.0.0" - google-closure-compiler-osx "^20190819.0.0" - google-closure-compiler-windows "^20190819.0.0" + google-closure-compiler-linux "^20230802.0.0" + google-closure-compiler-osx "^20230802.0.0" + google-closure-compiler-windows "^20230802.0.0" -google-closure-deps@^20210406.0.0: - version "20210406.0.0" - resolved "https://registry.npmjs.org/google-closure-deps/-/google-closure-deps-20210406.0.0.tgz" - integrity sha512-4mn6qZ8u4c/9fhebKccxyN882l5/0O4nuJ+ibuxDy0y7XMgolSLNF/Gmg1HEhEgX00CF/JBKrc/rw0WVjnlSfw== +google-closure-deps@20230802.0.0: + version "20230802.0.0" + resolved "https://registry.npmjs.org/google-closure-deps/-/google-closure-deps-20230802.0.0.tgz" + integrity sha512-5xhXO8xgxdXYZMsc8ZRzmnCm4prm0TcD33tcNr8l3OjSBovFO9MpgtrZxH/ZVnZMny6oOBmdeNqcO3afCYY9Lw== dependencies: minimatch "^3.0.4" yargs "^16.2.0" -google-closure-library@~20200315.0.0: - version "20200315.0.0" - resolved "https://registry.npmjs.org/google-closure-library/-/google-closure-library-20200315.0.0.tgz" - integrity sha512-LdCecdztxDvA9K+8b6E9aE4Hw7ABdYokfA7d5qW1sd/iy8O1l0+eXLlddE2JtW62gf8UjcPskBua4fU4J5lmHA== +google-closure-library@20230802.0.0: + version "20230802.0.0" + resolved "https://registry.npmjs.org/google-closure-library/-/google-closure-library-20230802.0.0.tgz" + integrity sha512-h2lBEX2tjWwfR+dhFukGER15vKY+4cA3nRqwKSOsBdyizlOxTAHOWbEYiGnMoom3/SrVeQ5uG0eKsbxQ3wbsxA== graceful-fs@^4.2.10, graceful-fs@^4.2.11, graceful-fs@^4.2.8: version "4.2.11" @@ -550,11 +504,6 @@ gulplog@^2.2.0: dependencies: glogg "^2.2.0" -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" - integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== - has-flag@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" @@ -1023,13 +972,6 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - supports-color@^7.1.0: version "7.2.0" resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz"