• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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