• 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      ];
319      var encoder = new jspb.BinaryEncoder();
320      testCases.forEach(function(c) {
321        encoder.writeZigzagVarint64String(c.original);
322      });
323      var buffer = encoder.end();
324      var zigzagDecoder = jspb.BinaryDecoder.alloc(buffer);
325      var varintDecoder = jspb.BinaryDecoder.alloc(buffer);
326      testCases.forEach(function(c) {
327        expect(zigzagDecoder.readZigzagVarint64String()).toEqual(c.original);
328        expect(varintDecoder.readUnsignedVarint64String()).toEqual(c.zigzag);
329      });
330    });
331  });
332
333  /**
334   * Tests reading and writing large strings
335   */
336  it('testLargeStrings', function() {
337    var encoder = new jspb.BinaryEncoder();
338
339    var len = 150000;
340    var long_string = '';
341    for (var i = 0; i < len; i++) {
342      long_string += 'a';
343    }
344
345    encoder.writeString(long_string);
346
347    var decoder = jspb.BinaryDecoder.alloc(encoder.end());
348
349    assertEquals(long_string, decoder.readString(len));
350  });
351
352  /**
353   * Test encoding and decoding utf-8.
354   */
355   it('testUtf8', function() {
356    var encoder = new jspb.BinaryEncoder();
357
358    var ascii = "ASCII should work in 3, 2, 1...";
359    var utf8_two_bytes = "©";
360    var utf8_three_bytes = "❄";
361    var utf8_four_bytes = "��";
362
363    encoder.writeString(ascii);
364    encoder.writeString(utf8_two_bytes);
365    encoder.writeString(utf8_three_bytes);
366    encoder.writeString(utf8_four_bytes);
367
368    var decoder = jspb.BinaryDecoder.alloc(encoder.end());
369
370    assertEquals(ascii, decoder.readString(ascii.length));
371    assertEquals(utf8_two_bytes, decoder.readString(utf8_two_bytes.length));
372    assertEquals(utf8_three_bytes, decoder.readString(utf8_three_bytes.length));
373    assertEquals(utf8_four_bytes, decoder.readString(utf8_four_bytes.length));
374   });
375
376  /**
377   * Verifies that misuse of the decoder class triggers assertions.
378   */
379  it('testDecodeErrors', function() {
380    // Reading a value past the end of the stream should trigger an assertion.
381    var decoder = jspb.BinaryDecoder.alloc([0, 1, 2]);
382    assertThrows(function() {decoder.readUint64()});
383
384    // Overlong varints should trigger assertions.
385    decoder.setBlock([255, 255, 255, 255, 255, 255,
386                      255, 255, 255, 255, 255, 0]);
387    assertThrows(function() {decoder.readUnsignedVarint64()});
388    decoder.reset();
389    assertThrows(function() {decoder.readSignedVarint64()});
390    decoder.reset();
391    assertThrows(function() {decoder.readZigzagVarint64()});
392    decoder.reset();
393    assertThrows(function() {decoder.readUnsignedVarint32()});
394  });
395
396
397  /**
398   * Tests encoding and decoding of unsigned integers.
399   */
400  it('testUnsignedIntegers', function() {
401    doTestUnsignedValue(
402        jspb.BinaryDecoder.prototype.readUint8,
403        jspb.BinaryEncoder.prototype.writeUint8,
404        1, 0xFF, Math.round);
405
406    doTestUnsignedValue(
407        jspb.BinaryDecoder.prototype.readUint16,
408        jspb.BinaryEncoder.prototype.writeUint16,
409        1, 0xFFFF, Math.round);
410
411    doTestUnsignedValue(
412        jspb.BinaryDecoder.prototype.readUint32,
413        jspb.BinaryEncoder.prototype.writeUint32,
414        1, 0xFFFFFFFF, Math.round);
415
416    doTestUnsignedValue(
417        jspb.BinaryDecoder.prototype.readUint64,
418        jspb.BinaryEncoder.prototype.writeUint64,
419        1, Math.pow(2, 64) - 1025, Math.round);
420  });
421
422
423  /**
424   * Tests encoding and decoding of signed integers.
425   */
426  it('testSignedIntegers', function() {
427    doTestSignedValue(
428        jspb.BinaryDecoder.prototype.readInt8,
429        jspb.BinaryEncoder.prototype.writeInt8,
430        1, -0x80, 0x7F, Math.round);
431
432    doTestSignedValue(
433        jspb.BinaryDecoder.prototype.readInt16,
434        jspb.BinaryEncoder.prototype.writeInt16,
435        1, -0x8000, 0x7FFF, Math.round);
436
437    doTestSignedValue(
438        jspb.BinaryDecoder.prototype.readInt32,
439        jspb.BinaryEncoder.prototype.writeInt32,
440        1, -0x80000000, 0x7FFFFFFF, Math.round);
441
442    doTestSignedValue(
443        jspb.BinaryDecoder.prototype.readInt64,
444        jspb.BinaryEncoder.prototype.writeInt64,
445        1, -Math.pow(2, 63), Math.pow(2, 63) - 513, Math.round);
446  });
447
448
449  /**
450   * Tests encoding and decoding of floats.
451   */
452  it('testFloats', function() {
453    /**
454     * @param {number} x
455     * @return {number}
456     */
457    function truncate(x) {
458      var temp = new Float32Array(1);
459      temp[0] = x;
460      return temp[0];
461    }
462    doTestSignedValue(
463        jspb.BinaryDecoder.prototype.readFloat,
464        jspb.BinaryEncoder.prototype.writeFloat,
465        jspb.BinaryConstants.FLOAT32_EPS,
466        -jspb.BinaryConstants.FLOAT32_MAX,
467        jspb.BinaryConstants.FLOAT32_MAX,
468        truncate);
469
470    doTestSignedValue(
471        jspb.BinaryDecoder.prototype.readDouble,
472        jspb.BinaryEncoder.prototype.writeDouble,
473        jspb.BinaryConstants.FLOAT64_EPS * 10,
474        -jspb.BinaryConstants.FLOAT64_MAX,
475        jspb.BinaryConstants.FLOAT64_MAX,
476        function(x) { return x; });
477  });
478});
479