1// Protocol Buffers - Google's data interchange format 2// Copyright 2008 Google Inc. All rights reserved. 3// https://developers.google.com/protocol-buffers/ 4// 5// Redistribution and use in source and binary forms, with or without 6// modification, are permitted provided that the following conditions are 7// met: 8// 9// * Redistributions of source code must retain the above copyright 10// notice, this list of conditions and the following disclaimer. 11// * Redistributions in binary form must reproduce the above 12// copyright notice, this list of conditions and the following disclaimer 13// in the documentation and/or other materials provided with the 14// distribution. 15// * Neither the name of Google Inc. nor the names of its 16// contributors may be used to endorse or promote products derived from 17// this software without specific prior written permission. 18// 19// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 31/** 32 * @fileoverview Test cases for jspb's binary protocol buffer decoder. 33 * 34 * There are two particular magic numbers that need to be pointed out - 35 * 2^64-1025 is the largest number representable as both a double and an 36 * unsigned 64-bit integer, and 2^63-513 is the largest number representable as 37 * both a double and a signed 64-bit integer. 38 * 39 * Test suite is written using Jasmine -- see http://jasmine.github.io/ 40 * 41 * @author aappleby@google.com (Austin Appleby) 42 */ 43 44goog.require('goog.testing.asserts'); 45goog.require('jspb.BinaryConstants'); 46goog.require('jspb.BinaryDecoder'); 47goog.require('jspb.BinaryEncoder'); 48goog.require('jspb.utils'); 49 50 51/** 52 * Tests encoding and decoding of unsigned types. 53 * @param {Function} readValue 54 * @param {Function} writeValue 55 * @param {number} epsilon 56 * @param {number} upperLimit 57 * @param {Function} filter 58 * @suppress {missingProperties|visibility} 59 */ 60function doTestUnsignedValue(readValue, 61 writeValue, epsilon, upperLimit, filter) { 62 var encoder = new jspb.BinaryEncoder(); 63 64 // Encode zero and limits. 65 writeValue.call(encoder, filter(0)); 66 writeValue.call(encoder, filter(epsilon)); 67 writeValue.call(encoder, filter(upperLimit)); 68 69 // Encode positive values. 70 for (var cursor = epsilon; cursor < upperLimit; cursor *= 1.1) { 71 writeValue.call(encoder, filter(cursor)); 72 } 73 74 var decoder = jspb.BinaryDecoder.alloc(encoder.end()); 75 76 // Check zero and limits. 77 assertEquals(filter(0), readValue.call(decoder)); 78 assertEquals(filter(epsilon), readValue.call(decoder)); 79 assertEquals(filter(upperLimit), readValue.call(decoder)); 80 81 // Check positive values. 82 for (var cursor = epsilon; cursor < upperLimit; cursor *= 1.1) { 83 if (filter(cursor) != readValue.call(decoder)) throw 'fail!'; 84 } 85 86 // Encoding values outside the valid range should assert. 87 assertThrows(function() {writeValue.call(encoder, -1);}); 88 assertThrows(function() {writeValue.call(encoder, upperLimit * 1.1);}); 89} 90 91 92/** 93 * Tests encoding and decoding of signed types. 94 * @param {Function} readValue 95 * @param {Function} writeValue 96 * @param {number} epsilon 97 * @param {number} lowerLimit 98 * @param {number} upperLimit 99 * @param {Function} filter 100 * @suppress {missingProperties} 101 */ 102function doTestSignedValue(readValue, 103 writeValue, epsilon, lowerLimit, upperLimit, filter) { 104 var encoder = new jspb.BinaryEncoder(); 105 106 // Encode zero and limits. 107 writeValue.call(encoder, filter(lowerLimit)); 108 writeValue.call(encoder, filter(-epsilon)); 109 writeValue.call(encoder, filter(0)); 110 writeValue.call(encoder, filter(epsilon)); 111 writeValue.call(encoder, filter(upperLimit)); 112 113 var inputValues = []; 114 115 // Encode negative values. 116 for (var cursor = lowerLimit; cursor < -epsilon; cursor /= 1.1) { 117 var val = filter(cursor); 118 writeValue.call(encoder, val); 119 inputValues.push(val); 120 } 121 122 // Encode positive values. 123 for (var cursor = epsilon; cursor < upperLimit; cursor *= 1.1) { 124 var val = filter(cursor); 125 writeValue.call(encoder, val); 126 inputValues.push(val); 127 } 128 129 var decoder = jspb.BinaryDecoder.alloc(encoder.end()); 130 131 // Check zero and limits. 132 assertEquals(filter(lowerLimit), readValue.call(decoder)); 133 assertEquals(filter(-epsilon), readValue.call(decoder)); 134 assertEquals(filter(0), readValue.call(decoder)); 135 assertEquals(filter(epsilon), readValue.call(decoder)); 136 assertEquals(filter(upperLimit), readValue.call(decoder)); 137 138 // Verify decoded values. 139 for (var i = 0; i < inputValues.length; i++) { 140 assertEquals(inputValues[i], readValue.call(decoder)); 141 } 142 143 // Encoding values outside the valid range should assert. 144 var pastLowerLimit = lowerLimit * 1.1; 145 var pastUpperLimit = upperLimit * 1.1; 146 if (pastLowerLimit !== -Infinity) { 147 expect(() => void writeValue.call(encoder, pastLowerLimit)).toThrow(); 148 } 149 if (pastUpperLimit !== Infinity) { 150 expect(() => void writeValue.call(encoder, pastUpperLimit)).toThrow(); 151 } 152} 153 154describe('binaryDecoderTest', function() { 155 /** 156 * Tests the decoder instance cache. 157 */ 158 it('testInstanceCache', /** @suppress {visibility} */ function() { 159 // Empty the instance caches. 160 jspb.BinaryDecoder.instanceCache_ = []; 161 162 // Allocating and then freeing a decoder should put it in the instance 163 // cache. 164 jspb.BinaryDecoder.alloc().free(); 165 166 assertEquals(1, jspb.BinaryDecoder.instanceCache_.length); 167 168 // Allocating and then freeing three decoders should leave us with three in 169 // the cache. 170 171 var decoder1 = jspb.BinaryDecoder.alloc(); 172 var decoder2 = jspb.BinaryDecoder.alloc(); 173 var decoder3 = jspb.BinaryDecoder.alloc(); 174 decoder1.free(); 175 decoder2.free(); 176 decoder3.free(); 177 178 assertEquals(3, jspb.BinaryDecoder.instanceCache_.length); 179 }); 180 181 182 describe('varint64', function() { 183 var /** !jspb.BinaryEncoder */ encoder; 184 var /** !jspb.BinaryDecoder */ decoder; 185 186 var hashA = String.fromCharCode(0x00, 0x00, 0x00, 0x00, 187 0x00, 0x00, 0x00, 0x00); 188 var hashB = String.fromCharCode(0x12, 0x34, 0x00, 0x00, 189 0x00, 0x00, 0x00, 0x00); 190 var hashC = String.fromCharCode(0x12, 0x34, 0x56, 0x78, 191 0x87, 0x65, 0x43, 0x21); 192 var hashD = String.fromCharCode(0xFF, 0xFF, 0xFF, 0xFF, 193 0xFF, 0xFF, 0xFF, 0xFF); 194 beforeEach(function() { 195 encoder = new jspb.BinaryEncoder(); 196 197 encoder.writeVarintHash64(hashA); 198 encoder.writeVarintHash64(hashB); 199 encoder.writeVarintHash64(hashC); 200 encoder.writeVarintHash64(hashD); 201 202 encoder.writeFixedHash64(hashA); 203 encoder.writeFixedHash64(hashB); 204 encoder.writeFixedHash64(hashC); 205 encoder.writeFixedHash64(hashD); 206 207 decoder = jspb.BinaryDecoder.alloc(encoder.end()); 208 }); 209 210 it('reads 64-bit integers as hash strings', function() { 211 assertEquals(hashA, decoder.readVarintHash64()); 212 assertEquals(hashB, decoder.readVarintHash64()); 213 assertEquals(hashC, decoder.readVarintHash64()); 214 assertEquals(hashD, decoder.readVarintHash64()); 215 216 assertEquals(hashA, decoder.readFixedHash64()); 217 assertEquals(hashB, decoder.readFixedHash64()); 218 assertEquals(hashC, decoder.readFixedHash64()); 219 assertEquals(hashD, decoder.readFixedHash64()); 220 }); 221 222 it('reads split 64 bit integers', function() { 223 function hexJoin(bitsLow, bitsHigh) { 224 return `0x${(bitsHigh >>> 0).toString(16)}:0x${ 225 (bitsLow >>> 0).toString(16)}`; 226 } 227 function hexJoinHash(hash64) { 228 jspb.utils.splitHash64(hash64); 229 return hexJoin(jspb.utils.split64Low, jspb.utils.split64High); 230 } 231 232 expect(decoder.readSplitVarint64(hexJoin)).toEqual(hexJoinHash(hashA)); 233 expect(decoder.readSplitVarint64(hexJoin)).toEqual(hexJoinHash(hashB)); 234 expect(decoder.readSplitVarint64(hexJoin)).toEqual(hexJoinHash(hashC)); 235 expect(decoder.readSplitVarint64(hexJoin)).toEqual(hexJoinHash(hashD)); 236 237 expect(decoder.readSplitFixed64(hexJoin)).toEqual(hexJoinHash(hashA)); 238 expect(decoder.readSplitFixed64(hexJoin)).toEqual(hexJoinHash(hashB)); 239 expect(decoder.readSplitFixed64(hexJoin)).toEqual(hexJoinHash(hashC)); 240 expect(decoder.readSplitFixed64(hexJoin)).toEqual(hexJoinHash(hashD)); 241 }); 242 }); 243 244 describe('sint64', function() { 245 var /** !jspb.BinaryDecoder */ decoder; 246 247 var hashA = 248 String.fromCharCode(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); 249 var hashB = 250 String.fromCharCode(0x12, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); 251 var hashC = 252 String.fromCharCode(0x12, 0x34, 0x56, 0x78, 0x87, 0x65, 0x43, 0x21); 253 var hashD = 254 String.fromCharCode(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF); 255 beforeEach(function() { 256 var encoder = new jspb.BinaryEncoder(); 257 258 encoder.writeZigzagVarintHash64(hashA); 259 encoder.writeZigzagVarintHash64(hashB); 260 encoder.writeZigzagVarintHash64(hashC); 261 encoder.writeZigzagVarintHash64(hashD); 262 263 decoder = jspb.BinaryDecoder.alloc(encoder.end()); 264 }); 265 266 it('reads 64-bit integers as decimal strings', function() { 267 const signed = true; 268 expect(decoder.readZigzagVarint64String()) 269 .toEqual(jspb.utils.hash64ToDecimalString(hashA, signed)); 270 expect(decoder.readZigzagVarint64String()) 271 .toEqual(jspb.utils.hash64ToDecimalString(hashB, signed)); 272 expect(decoder.readZigzagVarint64String()) 273 .toEqual(jspb.utils.hash64ToDecimalString(hashC, signed)); 274 expect(decoder.readZigzagVarint64String()) 275 .toEqual(jspb.utils.hash64ToDecimalString(hashD, signed)); 276 }); 277 278 it('reads 64-bit integers as hash strings', function() { 279 expect(decoder.readZigzagVarintHash64()).toEqual(hashA); 280 expect(decoder.readZigzagVarintHash64()).toEqual(hashB); 281 expect(decoder.readZigzagVarintHash64()).toEqual(hashC); 282 expect(decoder.readZigzagVarintHash64()).toEqual(hashD); 283 }); 284 285 it('reads split 64 bit zigzag integers', function() { 286 function hexJoin(bitsLow, bitsHigh) { 287 return `0x${(bitsHigh >>> 0).toString(16)}:0x${ 288 (bitsLow >>> 0).toString(16)}`; 289 } 290 function hexJoinHash(hash64) { 291 jspb.utils.splitHash64(hash64); 292 return hexJoin(jspb.utils.split64Low, jspb.utils.split64High); 293 } 294 295 expect(decoder.readSplitZigzagVarint64(hexJoin)) 296 .toEqual(hexJoinHash(hashA)); 297 expect(decoder.readSplitZigzagVarint64(hexJoin)) 298 .toEqual(hexJoinHash(hashB)); 299 expect(decoder.readSplitZigzagVarint64(hexJoin)) 300 .toEqual(hexJoinHash(hashC)); 301 expect(decoder.readSplitZigzagVarint64(hexJoin)) 302 .toEqual(hexJoinHash(hashD)); 303 }); 304 305 it('does zigzag encoding properly', function() { 306 // Test cases directly from the protobuf dev guide. 307 // https://engdoc.corp.google.com/eng/howto/protocolbuffers/developerguide/encoding.shtml?cl=head#types 308 var testCases = [ 309 {original: '0', zigzag: '0'}, 310 {original: '-1', zigzag: '1'}, 311 {original: '1', zigzag: '2'}, 312 {original: '-2', zigzag: '3'}, 313 {original: '2147483647', zigzag: '4294967294'}, 314 {original: '-2147483648', zigzag: '4294967295'}, 315 // 64-bit extremes, not in dev guide. 316 {original: '9223372036854775807', zigzag: '18446744073709551614'}, 317 {original: '-9223372036854775808', zigzag: '18446744073709551615'}, 318 // None of the above catch: bitsLow < 0 && bitsHigh > 0 && bitsHigh < 319 // 0x1FFFFF. The following used to be broken. 320 {original: '72000000000', zigzag: '144000000000'}, 321 ]; 322 var encoder = new jspb.BinaryEncoder(); 323 testCases.forEach(function(c) { 324 encoder.writeZigzagVarint64String(c.original); 325 }); 326 var buffer = encoder.end(); 327 var zigzagDecoder = jspb.BinaryDecoder.alloc(buffer); 328 var varintDecoder = jspb.BinaryDecoder.alloc(buffer); 329 testCases.forEach(function(c) { 330 expect(zigzagDecoder.readZigzagVarint64String()).toEqual(c.original); 331 expect(varintDecoder.readUnsignedVarint64String()).toEqual(c.zigzag); 332 }); 333 }); 334 }); 335 336 /** 337 * Tests reading and writing large strings 338 */ 339 it('testLargeStrings', function() { 340 var encoder = new jspb.BinaryEncoder(); 341 342 var len = 150000; 343 var long_string = ''; 344 for (var i = 0; i < len; i++) { 345 long_string += 'a'; 346 } 347 348 encoder.writeString(long_string); 349 350 var decoder = jspb.BinaryDecoder.alloc(encoder.end()); 351 352 assertEquals(long_string, decoder.readString(len)); 353 }); 354 355 /** 356 * Test encoding and decoding utf-8. 357 */ 358 it('testUtf8', function() { 359 var encoder = new jspb.BinaryEncoder(); 360 361 var ascii = "ASCII should work in 3, 2, 1..."; 362 var utf8_two_bytes = "©"; 363 var utf8_three_bytes = "❄"; 364 var utf8_four_bytes = ""; 365 366 encoder.writeString(ascii); 367 encoder.writeString(utf8_two_bytes); 368 encoder.writeString(utf8_three_bytes); 369 encoder.writeString(utf8_four_bytes); 370 371 var decoder = jspb.BinaryDecoder.alloc(encoder.end()); 372 373 assertEquals(ascii, decoder.readString(ascii.length)); 374 assertEquals(utf8_two_bytes, decoder.readString(utf8_two_bytes.length)); 375 assertEquals(utf8_three_bytes, decoder.readString(utf8_three_bytes.length)); 376 assertEquals(utf8_four_bytes, decoder.readString(utf8_four_bytes.length)); 377 }); 378 379 /** 380 * Verifies that misuse of the decoder class triggers assertions. 381 */ 382 it('testDecodeErrors', function() { 383 // Reading a value past the end of the stream should trigger an assertion. 384 var decoder = jspb.BinaryDecoder.alloc([0, 1, 2]); 385 assertThrows(function() {decoder.readUint64()}); 386 387 // Overlong varints should trigger assertions. 388 decoder.setBlock([255, 255, 255, 255, 255, 255, 389 255, 255, 255, 255, 255, 0]); 390 assertThrows(function() {decoder.readUnsignedVarint64()}); 391 decoder.reset(); 392 assertThrows(function() {decoder.readSignedVarint64()}); 393 decoder.reset(); 394 assertThrows(function() {decoder.readZigzagVarint64()}); 395 decoder.reset(); 396 assertThrows(function() {decoder.readUnsignedVarint32()}); 397 }); 398 399 400 /** 401 * Tests encoding and decoding of unsigned integers. 402 */ 403 it('testUnsignedIntegers', function() { 404 doTestUnsignedValue( 405 jspb.BinaryDecoder.prototype.readUint8, 406 jspb.BinaryEncoder.prototype.writeUint8, 407 1, 0xFF, Math.round); 408 409 doTestUnsignedValue( 410 jspb.BinaryDecoder.prototype.readUint16, 411 jspb.BinaryEncoder.prototype.writeUint16, 412 1, 0xFFFF, Math.round); 413 414 doTestUnsignedValue( 415 jspb.BinaryDecoder.prototype.readUint32, 416 jspb.BinaryEncoder.prototype.writeUint32, 417 1, 0xFFFFFFFF, Math.round); 418 419 doTestUnsignedValue( 420 jspb.BinaryDecoder.prototype.readUint64, 421 jspb.BinaryEncoder.prototype.writeUint64, 422 1, Math.pow(2, 64) - 1025, Math.round); 423 }); 424 425 426 /** 427 * Tests encoding and decoding of signed integers. 428 */ 429 it('testSignedIntegers', function() { 430 doTestSignedValue( 431 jspb.BinaryDecoder.prototype.readInt8, 432 jspb.BinaryEncoder.prototype.writeInt8, 433 1, -0x80, 0x7F, Math.round); 434 435 doTestSignedValue( 436 jspb.BinaryDecoder.prototype.readInt16, 437 jspb.BinaryEncoder.prototype.writeInt16, 438 1, -0x8000, 0x7FFF, Math.round); 439 440 doTestSignedValue( 441 jspb.BinaryDecoder.prototype.readInt32, 442 jspb.BinaryEncoder.prototype.writeInt32, 443 1, -0x80000000, 0x7FFFFFFF, Math.round); 444 445 doTestSignedValue( 446 jspb.BinaryDecoder.prototype.readInt64, 447 jspb.BinaryEncoder.prototype.writeInt64, 448 1, -Math.pow(2, 63), Math.pow(2, 63) - 513, Math.round); 449 }); 450 451 452 /** 453 * Tests encoding and decoding of floats. 454 */ 455 it('testFloats', function() { 456 /** 457 * @param {number} x 458 * @return {number} 459 */ 460 function truncate(x) { 461 var temp = new Float32Array(1); 462 temp[0] = x; 463 return temp[0]; 464 } 465 doTestSignedValue( 466 jspb.BinaryDecoder.prototype.readFloat, 467 jspb.BinaryEncoder.prototype.writeFloat, 468 jspb.BinaryConstants.FLOAT32_EPS, 469 -jspb.BinaryConstants.FLOAT32_MAX, 470 jspb.BinaryConstants.FLOAT32_MAX, 471 truncate); 472 473 doTestSignedValue( 474 jspb.BinaryDecoder.prototype.readDouble, 475 jspb.BinaryEncoder.prototype.writeDouble, 476 jspb.BinaryConstants.FLOAT64_EPS * 10, 477 -jspb.BinaryConstants.FLOAT64_MAX, 478 jspb.BinaryConstants.FLOAT64_MAX, 479 function(x) { return x; }); 480 }); 481}); 482