blob: 088015abb5b851306998d25e6540caaaab5e09de [file] [log] [blame] [edit]
/**
* @fileoverview This file contains helper code used by BinaryReader
* and BinaryWriter.
*
*
* @author [email protected] (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,
};