blob: 626118f788d951e7f46b689dd6c61b7869355e76 [file] [log] [blame] [edit]
// 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 Test cases for jspb's binary protocol buffer decoder.
*
* There are two particular magic numbers that need to be pointed out -
* 2^64-1025 is the largest number representable as both a double and an
* unsigned 64-bit integer, and 2^63-513 is the largest number representable as
* both a double and a signed 64-bit integer.
*
* Test suite is written using Jasmine -- see http://jasmine.github.io/
*
* @author [email protected] (Austin Appleby)
*/
goog.require('jspb.BinaryConstants');
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 {number} epsilon
* @param {number} upperLimit
* @param {!Function} filter
* @suppress {missingProperties|visibility}
*/
function doTestUnsignedValue(readValue,
writeValue, epsilon, upperLimit, filter) {
const encoder = new BinaryEncoder();
// Encode zero and limits.
writeValue.call(encoder, filter(0));
writeValue.call(encoder, filter(epsilon));
writeValue.call(encoder, filter(upperLimit));
// Encode positive values.
for (let cursor = epsilon; cursor < upperLimit; cursor *= 1.1) {
writeValue.call(encoder, filter(cursor));
}
const decoder = BinaryDecoder.alloc(encoder.end());
// Check zero and limits.
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(decoder)) throw 'fail!';
}
// Encoding values outside the valid range should assert.
expect(() => {
writeValue.call(encoder, -1);
}).toThrow();
expect(() => {
writeValue.call(encoder, upperLimit * 1.1);
}).toThrow();
}
/**
* Tests encoding and decoding of signed types.
* @param {!Function} readValue
* @param {!Function} writeValue
* @param {number} epsilon
* @param {number} lowerLimit
* @param {number} upperLimit
* @param {!Function} filter
* @suppress {missingProperties}
*/
function doTestSignedValue(readValue,
writeValue, epsilon, lowerLimit, upperLimit, filter) {
const encoder = new BinaryEncoder();
// Encode zero and limits.
writeValue.call(encoder, filter(lowerLimit));
writeValue.call(encoder, filter(-epsilon));
writeValue.call(encoder, filter(0));
writeValue.call(encoder, filter(epsilon));
writeValue.call(encoder, filter(upperLimit));
const inputValues = [];
// Encode negative values.
for (let cursor = lowerLimit; cursor < -epsilon; cursor /= 1.1) {
const val = filter(cursor);
writeValue.call(encoder, val);
inputValues.push(val);
}
// Encode positive values.
for (let cursor = epsilon; cursor < upperLimit; cursor *= 1.1) {
const val = filter(cursor);
writeValue.call(encoder, val);
inputValues.push(val);
}
const decoder = BinaryDecoder.alloc(encoder.end());
// Check zero and limits.
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(nestedDecoder)).toEqual(inputValues[i]);
}
// Encoding values outside the valid range should assert.
const pastLowerLimit = lowerLimit * 1.1;
const pastUpperLimit = upperLimit * 1.1;
if (pastLowerLimit !== -Infinity) {
expect(() => void writeValue.call(encoder, pastLowerLimit)).toThrow();
}
if (pastUpperLimit !== Infinity) {
expect(() => void writeValue.call(encoder, pastUpperLimit)).toThrow();
}
}
/**
* @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', () => {
// Empty the instance caches.
BinaryDecoder.resetInstanceCache();
// Allocating and then freeing a decoder should put it in the instance
// cache.
BinaryDecoder.alloc().free();
expect(BinaryDecoder.getInstanceCache().length).toEqual(1);
// Allocating and then freeing three decoders should leave us with three in
// the cache.
const decoder1 = BinaryDecoder.alloc();
const decoder2 = BinaryDecoder.alloc();
const decoder3 = BinaryDecoder.alloc();
decoder1.free();
decoder2.free();
decoder3.free();
expect(BinaryDecoder.getInstanceCache().length).toEqual(3);
});
describe('varint64', () => {
let /** !BinaryEncoder */ encoder;
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(() => {
encoder = new BinaryEncoder();
encoder.writeSplitVarint64(a.lo, a.hi);
encoder.writeSplitVarint64(b.lo, b.hi);
encoder.writeSplitVarint64(c.lo, c.hi);
encoder.writeSplitVarint64(d.lo, d.hi);
encoder.writeSplitFixed64(a.lo, a.hi);
encoder.writeSplitFixed64(b.lo, b.hi);
encoder.writeSplitFixed64(c.lo, c.hi);
encoder.writeSplitFixed64(d.lo, d.hi);
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)}`;
}
function hexJoinPair(p) {
return hexJoin(p.lo, p.hi);
}
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(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 /** !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 BinaryEncoder();
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 = BinaryDecoder.alloc(encoder.end());
});
it('reads 64-bit integers as decimal strings', () => {
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)}`;
}
function hexJoinPair(p) {
return hexJoin(p.lo, p.hi);
}
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 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' },
// 64-bit extremes, not in dev guide.
{ original: '9223372036854775807', zigzag: '18446744073709551614' },
{ original: '-9223372036854775808', zigzag: '18446744073709551615' },
];
const encoder = new BinaryEncoder();
testCases.forEach((c) => {
encoder.writeZigzagVarint64String(c.original);
});
const buffer = encoder.end();
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 */
it('testLargeStrings', () => {
const len = 150000;
let long_string = '';
for (let i = 0; i < len; i++) {
long_string += 'a';
}
const decoder = BinaryDecoder.alloc(encodeUtf8(long_string));
expect(decoder.readString(len, true)).toEqual(long_string);
});
/** Test encoding and decoding utf-8. */
it('testUtf8', () => {
const ascii = 'ASCII should work in 3, 2, 1...';
const utf8_two_bytes = '©';
const utf8_three_bytes = '❄';
const 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 = BinaryDecoder.alloc(new Uint8Array([
...encodeUtf8(ascii), ...encodeUtf8(utf8_two_bytes), ...encodeUtf8(utf8_three_bytes), ...encodeUtf8(utf8_four_bytes)
]));
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);
});
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();
});
describe('bytes', () => {
let /** Uint8Array */source;
let /** Uint8Array */sourceCopy;
beforeEach(() => {
source = new Uint8Array([1, 2, 3, 255, 1, 2, 8, 4]);
sourceCopy = new Uint8Array(source);
});
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(),
);
});
it('mutations to the source are not reflected in the output', () => {
const decoded = BinaryDecoder.alloc(source).readBytes(source.length);
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(
BinaryDecoder.readUint8, BinaryEncoder.prototype.writeUint8,
1, 0xFF, Math.round);
doTestUnsignedValue(
BinaryDecoder.readUint16, BinaryEncoder.prototype.writeUint16,
1, 0xFFFF, Math.round);
doTestUnsignedValue(
BinaryDecoder.readUint32, BinaryEncoder.prototype.writeUint32,
1, 0xFFFFFFFF, Math.round);
doTestUnsigned64BitIntValue(BinaryDecoder.readUint64,
BinaryEncoder.prototype.writeUint64);
});
/** Tests encoding and decoding of signed integers. */
it('testSignedIntegers', () => {
doTestSignedValue(
BinaryDecoder.readInt8, BinaryEncoder.prototype.writeInt8, 1,
-0x80, 0x7F, Math.round);
doTestSignedValue(
BinaryDecoder.readInt16, BinaryEncoder.prototype.writeInt16,
1, -0x8000, 0x7FFF, Math.round);
doTestSignedValue(
BinaryDecoder.readInt32, BinaryEncoder.prototype.writeInt32,
1, -0x80000000, 0x7FFFFFFF, Math.round);
doTestSigned64BitIntValue(
BinaryDecoder.readInt64, BinaryEncoder.prototype.writeInt64);
});
/** Tests encoding and decoding of floats. */
it('testFloats', () => {
/**
* @param {number} x
* @return {number}
*/
function truncate(x) {
const temp = new Float32Array(1);
temp[0] = x;
return temp[0];
}
doTestSignedValue(
BinaryDecoder.readFloat, BinaryEncoder.prototype.writeFloat,
BinaryConstants.FLOAT32_EPS, -BinaryConstants.FLOAT32_MAX,
BinaryConstants.FLOAT32_MAX, truncate);
doTestSignedValue(
BinaryDecoder.readDouble, BinaryEncoder.prototype.writeDouble,
BinaryConstants.FLOAT64_EPS * 10, -BinaryConstants.FLOAT64_MAX,
BinaryConstants.FLOAT64_MAX, (x) => x);
});
});