| /** | |
| * @fileoverview This file contains helper code used by BinaryReader | |
| * and BinaryWriter. | |
| * | |
| * | |
| * @author aappleby@google.com (Austin Appleby) | |
| */ | |
| goog.module('jspb.utils'); | |
| goog.module.declareLegacyNamespace(); | |
| const BinaryConstants = goog.require('jspb.BinaryConstants'); | |
| const { assert } = goog.require('goog.asserts'); | |
| const { isBigIntAvailable } = goog.require('jspb.internal_options'); | |
| const { ByteSource } = goog.require('jspb.binary.bytesource'); | |
| 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'); | |
| /** @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 | |
| * have to split them into two 32-bit halves and do the math manually. | |
| * | |
| * Instead of instantiating and passing small structures around to do this, we | |
| * instead just use two global temporary values. This one stores the low 32 | |
| * bits of a split value - for example, if the original value was a 64-bit | |
| * 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} | |
| */ | |
| let split64Low = 0; | |
| /** | |
| * And correspondingly, this temporary variable will contain the high 32 bits | |
| * of whatever value was split. | |
| * @type {number} | |
| */ | |
| 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. | |
| */ | |
| function splitUint64(value) { | |
| // Extract low 32 bits and high 32 bits as unsigned integers. | |
| 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. | |
| */ | |
| function splitInt64(value) { | |
| // Perform two's complement conversion if the sign bit was set. | |
| 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); | |
| } | |
| } | |
| /** | |
| * 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. | |
| */ | |
| function splitZigzag64(value) { | |
| // Convert to sign-magnitude and scale by 2 before we split the value. | |
| const sign = (value < 0); | |
| value = Math.abs(value) * 2; | |
| 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. | |
| if (sign) { | |
| if (lowBits == 0) { | |
| if (highBits == 0) { | |
| lowBits = 0xFFFFFFFF; | |
| highBits = 0xFFFFFFFF; | |
| } else { | |
| highBits--; | |
| lowBits = 0xFFFFFFFF; | |
| } | |
| } else { | |
| lowBits--; | |
| } | |
| } | |
| 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|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|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-byte array into two 32-bit numbers and stores them in the temp | |
| * values above. | |
| * @param {!Array<number>} bytes | |
| */ | |
| 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. 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} | |
| */ | |
| 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. Value will be | |
| * returned as a string if it outside of the safe integer range. | |
| * @param {number} bitsLow | |
| * @param {number} bitsHigh | |
| * @return {number} | |
| */ | |
| function joinInt64(bitsLow, bitsHigh) { | |
| // If the high bit is set, do a manual two's complement conversion. | |
| const sign = (bitsHigh & 0x80000000); | |
| if (sign) { | |
| bitsLow = (~bitsLow + 1) >>> 0; | |
| bitsHigh = ~bitsHigh >>> 0; | |
| if (bitsLow == 0) { | |
| bitsHigh = (bitsHigh + 1) >>> 0; | |
| } | |
| } | |
| 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 | |
| * zig-zag encoding. Invokes the provided function to produce final result. | |
| * | |
| * @param {number} bitsLow | |
| * @param {number} bitsHigh | |
| * @param {function(number, number): T} convert Conversion function to produce | |
| * the result value, takes parameters (lowBits, highBits). | |
| * @return {T} | |
| * @template T | |
| */ | |
| 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) | |
| // | |
| // To do this in 32 bits, we can get a 32-bit sign-flipping mask from the | |
| // high word. | |
| // Then we can operate on each word individually, with the addition of the | |
| // "carry" to get the most significant bit from the low word into the high | |
| // word. | |
| const signFlipMask = bitsHigh >> 31; | |
| bitsHigh = (bitsHigh << 1 | bitsLow >>> 31) ^ signFlipMask; | |
| bitsLow = (bitsLow << 1) ^ signFlipMask; | |
| return convert(bitsLow, bitsHigh); | |
| } | |
| /** | |
| * Joins two 32-bit values into a 64-bit unsigned integer and applies zigzag | |
| * decoding. Precision will be lost if the result is greater than 2^52. | |
| * @param {number} bitsLow | |
| * @param {number} bitsHigh | |
| * @return {number} | |
| */ | |
| 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 | |
| * complement encoding. Invokes the provided function to produce final result. | |
| * | |
| * @param {number} bitsLow | |
| * @param {number} bitsHigh | |
| * @param {function(number, number): T} convert Conversion function to produce | |
| * the result value, takes parameters (lowBits, highBits). | |
| * @return {T} | |
| * @template T | |
| */ | |
| function fromZigzag64(bitsLow, bitsHigh, convert) { | |
| // 64 bit math is: | |
| // signmask = (zigzag & 1) ? -1 : 0; | |
| // twosComplement = (zigzag >> 1) ^ signmask; | |
| // | |
| // To work with 32 bit, we can operate on both but "carry" the lowest bit | |
| // from the high word by shifting it up 31 bits to be the most significant bit | |
| // of the low word. | |
| const signFlipMask = -(bitsLow & 1); | |
| bitsLow = ((bitsLow >>> 1) | (bitsHigh << 31)) ^ signFlipMask; | |
| bitsHigh = (bitsHigh >>> 1) ^ signFlipMask; | |
| return convert(bitsLow, bitsHigh); | |
| } | |
| /** | |
| * Joins two 32-bit values into a 32-bit IEEE floating point number and | |
| * converts it back into a Javascript number. | |
| * @param {number} bitsLow The low 32 bits of the binary number; | |
| * @param {number} bitsHigh The high 32 bits of the binary number. | |
| * @return {number} | |
| */ | |
| function joinFloat32(bitsLow, bitsHigh) { | |
| const sign = ((bitsLow >> 31) * 2 + 1); | |
| const exp = (bitsLow >>> 23) & 0xFF; | |
| const mant = bitsLow & 0x7FFFFF; | |
| if (exp == 0xFF) { | |
| if (mant) { | |
| return NaN; | |
| } else { | |
| return sign * Infinity; | |
| } | |
| } | |
| if (exp == 0) { | |
| // Denormal. | |
| return sign * Math.pow(2, -149) * mant; | |
| } else { | |
| return sign * Math.pow(2, exp - 150) * (mant + Math.pow(2, 23)); | |
| } | |
| } | |
| /** | |
| * Joins two 32-bit values into a 64-bit IEEE floating point number and | |
| * converts it back into a Javascript number. | |
| * @param {number} bitsLow The low 32 bits of the binary number; | |
| * @param {number} bitsHigh The high 32 bits of the binary number. | |
| * @return {number} | |
| */ | |
| 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) { | |
| return NaN; | |
| } else { | |
| return sign * Infinity; | |
| } | |
| } | |
| if (exp == 0) { | |
| // Denormal. | |
| return sign * Math.pow(2, -1074) * mant; | |
| } else { | |
| return sign * Math.pow(2, exp - 1075) * (mant + BinaryConstants.TWO_TO_52); | |
| } | |
| } | |
| /** | |
| * Losslessly converts a 64-bit unsigned 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. | |
| */ | |
| 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 '' + (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. | |
| // Split 32:32 representation into 16:24:24 representation so our | |
| // intermediate digits don't overflow. | |
| 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. | |
| // 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. | |
| const base = 10000000; | |
| if (digitA >= base) { | |
| digitB += (digitA / base) >>> 0; | |
| digitA %= base; | |
| } | |
| if (digitB >= base) { | |
| digitC += (digitB / base) >>> 0; | |
| digitB %= base; | |
| } | |
| // 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 | |
| * 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. | |
| */ | |
| function joinSignedDecimalString(bitsLow, bitsHigh) { | |
| const negative = (bitsHigh & 0x80000000); | |
| if (negative) { | |
| if (isBigIntAvailable()) { | |
| return '' + | |
| ((BigInt(bitsHigh | 0) << BigInt(32)) | BigInt(bitsLow >>> 0)); | |
| } | |
| return joinNegativeDecimalStringFallback(bitsLow, bitsHigh); | |
| } else { | |
| return joinUnsignedDecimalString(bitsLow, bitsHigh); | |
| } | |
| } | |
| /** | |
| * 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. | |
| */ | |
| function joinSignedNumberOrDecimalString(bitsLow, bitsHigh) { | |
| const possiblyUnsafe = joinInt64(bitsLow, bitsHigh); | |
| if (Number.isSafeInteger(possiblyUnsafe)) { | |
| return possiblyUnsafe; | |
| } | |
| return joinSignedDecimalString(bitsLow, bitsHigh); | |
| } | |
| /** | |
| * 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. | |
| */ | |
| function joinUnsignedNumberOrDecimalString(bitsLow, bitsHigh) { | |
| bitsHigh >>>= 0; | |
| const possiblyUnsafe = joinUint64(bitsLow, bitsHigh); | |
| if (Number.isSafeInteger(possiblyUnsafe)) { | |
| return possiblyUnsafe; | |
| } | |
| return joinUnsignedDecimalString(bitsLow, bitsHigh); | |
| } | |
| /** | |
| * 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. | |
| */ | |
| 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. Only the lower 64 bits of the | |
| * value are kept. | |
| * @param {string} value The decimal string to convert. | |
| */ | |
| function splitDecimalString(value) { | |
| assert(value.length > 0); | |
| // 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; | |
| } | |
| 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); | |
| } | |
| } | |
| /** | |
| * 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. | |
| */ | |
| 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; | |
| } | |
| } | |
| if (firstDigitIndex /* != 0 */) { | |
| const [negLow, negHigh] = negate(split64Low, split64High); | |
| split64Low = negLow; | |
| split64High = negHigh; | |
| } | |
| } | |
| /** | |
| * @param {number} lowBits | |
| * @param {number} highBits | |
| * @return {!Array<number>} [low, high] words of the result. | |
| * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Signed_32-bit_integers | |
| */ | |
| 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]; | |
| } | |
| /** | |
| * Counts the number of contiguous varints in a buffer. | |
| * @param {!Uint8Array} buffer The buffer to scan. | |
| * @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. | |
| */ | |
| function countVarints(buffer, start, end) { | |
| // Count how many high bits of each byte were set in the buffer. | |
| let count = 0; | |
| for (let i = start; i < end; i++) { | |
| count += buffer[i] >> 7; | |
| } | |
| // The number of varints in the buffer equals the size of the buffer minus | |
| // the number of non-terminal bytes in the buffer (those with the high bit | |
| // set). | |
| return (end - start) - count; | |
| } | |
| /** | |
| * Counts the number of contiguous varint fields with the given field number in | |
| * the buffer. | |
| * @param {!Uint8Array} buffer The buffer to scan. | |
| * @param {number} start The starting point in the buffer to scan. | |
| * @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. | |
| */ | |
| 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. | |
| while (cursor < end) { | |
| // Skip the field tag, or exit if we find a non-matching tag. | |
| if (buffer[cursor++] != tag) return count; | |
| // Field tag matches, we've found a valid field. | |
| count++; | |
| // Skip the varint. | |
| while (1) { | |
| 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. | |
| let temp = tag; | |
| while (temp > 128) { | |
| if (buffer[cursor] != ((temp & 0x7F) | 0x80)) return count; | |
| cursor++; | |
| temp >>= 7; | |
| } | |
| if (buffer[cursor++] != temp) return count; | |
| // Field tag matches, we've found a valid field. | |
| count++; | |
| // Skip the varint. | |
| while (1) { | |
| const x = buffer[cursor++]; | |
| if ((x & 0x80) == 0) break; | |
| } | |
| } | |
| } | |
| return count; | |
| } | |
| /** | |
| * Counts the number of contiguous fixed32 fields with the given tag in the | |
| * buffer. | |
| * @param {!Uint8Array} buffer The buffer to scan. | |
| * @param {number} start The starting point in the buffer to scan. | |
| * @param {number} end The end point in the buffer to scan. | |
| * @param {number} tag The tag value to count. | |
| * @param {number} stride The number of bytes to skip per field. | |
| * @return {number} The number of fields with a matching tag in the buffer. | |
| * @private | |
| */ | |
| 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. | |
| while (cursor < end) { | |
| // Skip the field tag, or exit if we find a non-matching tag. | |
| if (buffer[cursor++] != tag) return count; | |
| // Field tag matches, we've found a valid field. | |
| count++; | |
| // Skip the value. | |
| cursor += stride; | |
| } | |
| } else { | |
| while (cursor < end) { | |
| // Skip the field tag, or exit if we find a non-matching tag. | |
| let temp = tag; | |
| while (temp > 128) { | |
| if (buffer[cursor++] != ((temp & 0x7F) | 0x80)) return count; | |
| temp >>= 7; | |
| } | |
| if (buffer[cursor++] != temp) return count; | |
| // Field tag matches, we've found a valid field. | |
| count++; | |
| // Skip the value. | |
| cursor += stride; | |
| } | |
| } | |
| return count; | |
| } | |
| /** | |
| * Counts the number of contiguous fixed32 fields with the given field number | |
| * in the buffer. | |
| * @param {!Uint8Array} buffer The buffer to scan. | |
| * @param {number} start The starting point in the buffer to scan. | |
| * @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. | |
| */ | |
| function countFixed32Fields(buffer, start, end, field) { | |
| const tag = field * 8 + BinaryConstants.WireType.FIXED32; | |
| return countFixedFields_(buffer, start, end, tag, 4); | |
| } | |
| /** | |
| * Counts the number of contiguous fixed64 fields with the given field number | |
| * in the buffer. | |
| * @param {!Uint8Array} buffer The buffer to scan. | |
| * @param {number} start The starting point in the buffer to scan. | |
| * @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. | |
| */ | |
| function countFixed64Fields(buffer, start, end, field) { | |
| const tag = field * 8 + BinaryConstants.WireType.FIXED64; | |
| return countFixedFields_(buffer, start, end, tag, 8); | |
| } | |
| /** | |
| * Counts the number of contiguous delimited fields with the given field number | |
| * in the buffer. | |
| * @param {!Uint8Array} buffer The buffer to scan. | |
| * @param {number} start The starting point in the buffer to scan. | |
| * @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. | |
| */ | |
| 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. | |
| let temp = tag; | |
| while (temp > 128) { | |
| if (buffer[cursor++] != ((temp & 0x7F) | 0x80)) return count; | |
| temp >>= 7; | |
| } | |
| if (buffer[cursor++] != temp) return count; | |
| // Field tag matches, we've found a valid field. | |
| count++; | |
| // Decode the length prefix. | |
| let length = 0; | |
| let shift = 1; | |
| while (1) { | |
| temp = buffer[cursor++]; | |
| length += (temp & 0x7f) * shift; | |
| shift *= 128; | |
| if ((temp & 0x80) == 0) break; | |
| } | |
| // Advance the cursor past the blob. | |
| cursor += length; | |
| } | |
| return count; | |
| } | |
| /** | |
| * 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} | |
| */ | |
| 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); | |
| } | |
| if (data.constructor === ArrayBuffer) { | |
| data = /** @type {!ArrayBuffer} */ (data); | |
| return /** @type {!Uint8Array} */ (new Uint8Array(data)); | |
| } | |
| if (data.constructor === Array) { | |
| data = /** @type {!Array<number>} */ (data); | |
| return /** @type {!Uint8Array} */ (new Uint8Array(data)); | |
| } | |
| if (data.constructor === String) { | |
| data = /** @type {string} */ (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 (a subclass of Uint8Array). | |
| data = /** @type {!Uint8Array} */ (data); | |
| // 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)); | |
| } | |
| 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; | |
| } | |
| /** @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, | |
| }; |