// Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ // // 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 aappleby@google.com (Austin Appleby) */ goog.require('goog.testing.asserts'); 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) { var 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 (var cursor = epsilon; cursor < upperLimit; cursor *= 1.1) { writeValue.call(encoder, filter(cursor)); } var decoder = jspb.BinaryDecoder.alloc(encoder.end()); // Check zero and limits. assertEquals(filter(0), readValue.call(decoder)); assertEquals(filter(epsilon), readValue.call(decoder)); assertEquals(filter(upperLimit), readValue.call(decoder)); // Check positive values. for (var cursor = epsilon; cursor < upperLimit; cursor *= 1.1) { if (filter(cursor) != readValue.call(decoder)) throw 'fail!'; } // Encoding values outside the valid range should assert. assertThrows(function() {writeValue.call(encoder, -1);}); assertThrows(function() {writeValue.call(encoder, upperLimit * 1.1);}); } /** * 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) { var 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)); var inputValues = []; // Encode negative values. for (var cursor = lowerLimit; cursor < -epsilon; cursor /= 1.1) { var val = filter(cursor); writeValue.call(encoder, val); inputValues.push(val); } // Encode positive values. for (var cursor = epsilon; cursor < upperLimit; cursor *= 1.1) { var val = filter(cursor); writeValue.call(encoder, val); inputValues.push(val); } var decoder = jspb.BinaryDecoder.alloc(encoder.end()); // Check zero and limits. assertEquals(filter(lowerLimit), readValue.call(decoder)); assertEquals(filter(-epsilon), readValue.call(decoder)); assertEquals(filter(0), readValue.call(decoder)); assertEquals(filter(epsilon), readValue.call(decoder)); assertEquals(filter(upperLimit), readValue.call(decoder)); // Verify decoded values. for (var i = 0; i < inputValues.length; i++) { assertEquals(inputValues[i], readValue.call(decoder)); } // Encoding values outside the valid range should assert. var pastLowerLimit = lowerLimit * 1.1; var 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', function() { /** * Tests the decoder instance cache. */ it('testInstanceCache', /** @suppress {visibility} */ function() { // Empty the instance caches. jspb.BinaryDecoder.instanceCache_ = []; // Allocating and then freeing a decoder should put it in the instance // cache. jspb.BinaryDecoder.alloc().free(); assertEquals(1, jspb.BinaryDecoder.instanceCache_.length); // Allocating and then freeing three decoders should leave us with three in // the cache. var decoder1 = jspb.BinaryDecoder.alloc(); var decoder2 = jspb.BinaryDecoder.alloc(); var decoder3 = jspb.BinaryDecoder.alloc(); decoder1.free(); decoder2.free(); decoder3.free(); assertEquals(3, jspb.BinaryDecoder.instanceCache_.length); }); describe('varint64', function() { var /** !jspb.BinaryEncoder */ encoder; var /** !jspb.BinaryDecoder */ decoder; var hashA = String.fromCharCode(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); var hashB = String.fromCharCode(0x12, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); var hashC = String.fromCharCode(0x12, 0x34, 0x56, 0x78, 0x87, 0x65, 0x43, 0x21); var hashD = String.fromCharCode(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF); beforeEach(function() { 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', function() { assertEquals(hashA, decoder.readVarintHash64()); assertEquals(hashB, decoder.readVarintHash64()); assertEquals(hashC, decoder.readVarintHash64()); assertEquals(hashD, decoder.readVarintHash64()); assertEquals(hashA, decoder.readFixedHash64()); assertEquals(hashB, decoder.readFixedHash64()); assertEquals(hashC, decoder.readFixedHash64()); assertEquals(hashD, decoder.readFixedHash64()); }); it('reads split 64 bit integers', function() { 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.split64Low, jspb.utils.split64High); } 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', function() { var /** !jspb.BinaryDecoder */ decoder; var hashA = String.fromCharCode(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); var hashB = String.fromCharCode(0x12, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); var hashC = String.fromCharCode(0x12, 0x34, 0x56, 0x78, 0x87, 0x65, 0x43, 0x21); var hashD = String.fromCharCode(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF); beforeEach(function() { var 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', function() { 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', function() { 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() { 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.split64Low, jspb.utils.split64High); } 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', function() { // Test cases directly from the protobuf dev guide. // https://engdoc.corp.google.com/eng/howto/protocolbuffers/developerguide/encoding.shtml?cl=head#types var 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'}, ]; var encoder = new jspb.BinaryEncoder(); testCases.forEach(function(c) { encoder.writeZigzagVarint64String(c.original); }); var buffer = encoder.end(); var zigzagDecoder = jspb.BinaryDecoder.alloc(buffer); var 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', function() { var encoder = new jspb.BinaryEncoder(); var len = 150000; var long_string = ''; for (var i = 0; i < len; i++) { long_string += 'a'; } encoder.writeString(long_string); var decoder = jspb.BinaryDecoder.alloc(encoder.end()); assertEquals(long_string, decoder.readString(len)); }); /** * Test encoding and decoding utf-8. */ it('testUtf8', function() { var encoder = new jspb.BinaryEncoder(); var ascii = "ASCII should work in 3, 2, 1..."; var utf8_two_bytes = "©"; var utf8_three_bytes = "❄"; var utf8_four_bytes = "😁"; encoder.writeString(ascii); encoder.writeString(utf8_two_bytes); encoder.writeString(utf8_three_bytes); encoder.writeString(utf8_four_bytes); var decoder = jspb.BinaryDecoder.alloc(encoder.end()); assertEquals(ascii, decoder.readString(ascii.length)); assertEquals(utf8_two_bytes, decoder.readString(utf8_two_bytes.length)); assertEquals(utf8_three_bytes, decoder.readString(utf8_three_bytes.length)); assertEquals(utf8_four_bytes, decoder.readString(utf8_four_bytes.length)); }); /** * Verifies that misuse of the decoder class triggers assertions. */ it('testDecodeErrors', function() { // Reading a value past the end of the stream should trigger an assertion. var decoder = jspb.BinaryDecoder.alloc([0, 1, 2]); assertThrows(function() {decoder.readUint64()}); // Overlong varints should trigger assertions. decoder.setBlock([255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0]); assertThrows(function() {decoder.readUnsignedVarint64()}); decoder.reset(); assertThrows(function() {decoder.readSignedVarint64()}); decoder.reset(); assertThrows(function() {decoder.readZigzagVarint64()}); decoder.reset(); assertThrows(function() {decoder.readUnsignedVarint32()}); }); /** * Tests encoding and decoding of unsigned integers. */ it('testUnsignedIntegers', function() { 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.readUint64, jspb.BinaryEncoder.prototype.writeUint64, 1, Math.pow(2, 64) - 1025, Math.round); }); /** * Tests encoding and decoding of signed integers. */ it('testSignedIntegers', function() { 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.readInt64, jspb.BinaryEncoder.prototype.writeInt64, 1, -Math.pow(2, 63), Math.pow(2, 63) - 513, Math.round); }); /** * Tests encoding and decoding of floats. */ it('testFloats', function() { /** * @param {number} x * @return {number} */ function truncate(x) { var 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; }); }); });