| // 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.BinaryDecoder'); |
| goog.require('jspb.BinaryEncoder'); |
| |
| goog.require('jspb.utils'); |
| |
| |
| /** |
| * 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 jspb.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 = jspb.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)); |
| |
| // Check positive values. |
| for (let cursor = epsilon; cursor < upperLimit; cursor *= 1.1) { |
| if (filter(cursor) != readValue.call(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 jspb.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) { |
| let 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 = jspb.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)); |
| |
| // Verify decoded values. |
| for (let i = 0; i < inputValues.length; i++) { |
| expect(readValue.call(decoder)).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(); |
| } |
| } |
| |
| describe('binaryDecoderTest', () => { |
| /** |
| * Tests the decoder instance cache. |
| */ |
| it('testInstanceCache', /** @suppress {visibility} */ () => { |
| // Empty the instance caches. |
| jspb.BinaryDecoder.instanceCache_ = []; |
| |
| // Allocating and then freeing a decoder should put it in the instance |
| // cache. |
| jspb.BinaryDecoder.alloc().free(); |
| |
| expect(jspb.BinaryDecoder.getInstanceCacheLength()).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(); |
| decoder1.free(); |
| decoder2.free(); |
| decoder3.free(); |
| |
| expect(jspb.BinaryDecoder.getInstanceCacheLength()).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); |
| |
| encoder.writeFixedHash64(hashA); |
| encoder.writeFixedHash64(hashB); |
| encoder.writeFixedHash64(hashC); |
| encoder.writeFixedHash64(hashD); |
| |
| decoder = jspb.BinaryDecoder.alloc(encoder.end()); |
| }); |
| |
| 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()); |
| |
| expect(hashA).toEqual(decoder.readFixedHash64()); |
| expect(hashB).toEqual(decoder.readFixedHash64()); |
| expect(hashC).toEqual(decoder.readFixedHash64()); |
| expect(hashD).toEqual(decoder.readFixedHash64()); |
| }); |
| |
| it('reads split 64 bit integers', () => { |
| function hexJoin(bitsLow, bitsHigh) { |
| 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()); |
| } |
| |
| 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(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)); |
| }); |
| }); |
| |
| 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); |
| beforeEach(() => { |
| const encoder = new jspb.BinaryEncoder(); |
| |
| encoder.writeZigzagVarintHash64(hashA); |
| encoder.writeZigzagVarintHash64(hashB); |
| encoder.writeZigzagVarintHash64(hashC); |
| encoder.writeZigzagVarintHash64(hashD); |
| |
| decoder = jspb.BinaryDecoder.alloc(encoder.end()); |
| }); |
| |
| it('reads 64-bit integers as decimal strings', () => { |
| 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); |
| }); |
| |
| it('reads split 64 bit zigzag integers', () => { |
| function hexJoin(bitsLow, bitsHigh) { |
| return `0x${(bitsHigh >>> 0).toString(16)}:0x${ |
| (bitsLow >>> 0).toString(16)}`; |
| } |
| function hexJoinHash(hash64) { |
| jspb.utils.splitHash64(hash64); |
| return hexJoin(jspb.utils.getSplit64Low(), jspb.utils.getSplit64High()); |
| } |
| |
| expect(decoder.readSplitZigzagVarint64(hexJoin)) |
| .toEqual(hexJoinHash(hashA)); |
| expect(decoder.readSplitZigzagVarint64(hexJoin)) |
| .toEqual(hexJoinHash(hashB)); |
| expect(decoder.readSplitZigzagVarint64(hexJoin)) |
| .toEqual(hexJoinHash(hashC)); |
| expect(decoder.readSplitZigzagVarint64(hexJoin)) |
| .toEqual(hexJoinHash(hashD)); |
| }); |
| |
| it('does zigzag encoding properly', () => { |
| // 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: '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'}, |
| ]; |
| const encoder = new jspb.BinaryEncoder(); |
| testCases.forEach(function(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); |
| }); |
| }); |
| }); |
| |
| /** |
| * 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()); |
| |
| expect(decoder.readString(len, true)).toEqual(long_string); |
| }); |
| |
| /** |
| * 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); |
| |
| const decoder = jspb.BinaryDecoder.alloc(encoder.end()); |
| |
| 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)); |
| }); |
| |
| /** |
| * 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(); |
| }); |
| |
| /** |
| * 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(); |
| }); |
| |
| |
| /** |
| * Tests encoding and decoding of unsigned integers. |
| */ |
| it('testUnsignedIntegers', () => { |
| doTestUnsignedValue( |
| jspb.BinaryDecoder.prototype.readUint8, |
| jspb.BinaryEncoder.prototype.writeUint8, 1, 0xFF, Math.round); |
| |
| doTestUnsignedValue( |
| jspb.BinaryDecoder.prototype.readUint16, |
| jspb.BinaryEncoder.prototype.writeUint16, 1, 0xFFFF, Math.round); |
| |
| doTestUnsignedValue( |
| jspb.BinaryDecoder.prototype.readUint32, |
| jspb.BinaryEncoder.prototype.writeUint32, 1, 0xFFFFFFFF, Math.round); |
| |
| doTestUnsignedValue( |
| jspb.BinaryDecoder.prototype.readUnsignedVarint32, |
| jspb.BinaryEncoder.prototype.writeUnsignedVarint32, 1, 0xFFFFFFFF, |
| Math.round); |
| |
| doTestUnsignedValue( |
| jspb.BinaryDecoder.prototype.readUint64, |
| jspb.BinaryEncoder.prototype.writeUint64, 1, Math.pow(2, 64) - 1025, |
| Math.round); |
| |
| doTestUnsignedValue( |
| jspb.BinaryDecoder.prototype.readUnsignedVarint64, |
| jspb.BinaryEncoder.prototype.writeUnsignedVarint64, 1, |
| Math.pow(2, 64) - 1025, Math.round); |
| }); |
| |
| |
| /** |
| * Tests encoding and decoding of signed integers. |
| */ |
| it('testSignedIntegers', () => { |
| doTestSignedValue( |
| jspb.BinaryDecoder.prototype.readInt8, |
| jspb.BinaryEncoder.prototype.writeInt8, 1, -0x80, 0x7F, Math.round); |
| |
| doTestSignedValue( |
| jspb.BinaryDecoder.prototype.readInt16, |
| jspb.BinaryEncoder.prototype.writeInt16, 1, -0x8000, 0x7FFF, |
| Math.round); |
| |
| doTestSignedValue( |
| jspb.BinaryDecoder.prototype.readInt32, |
| jspb.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); |
| }); |
| |
| |
| /** |
| * 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( |
| jspb.BinaryDecoder.prototype.readFloat, |
| jspb.BinaryEncoder.prototype.writeFloat, |
| jspb.BinaryConstants.FLOAT32_EPS, -jspb.BinaryConstants.FLOAT32_MAX, |
| jspb.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; |
| }); |
| }); |
| }); |