Home | History | Annotate | Download | only in binary
      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 BinaryEncode defines methods for encoding Javascript values
     33  * into arrays of bytes compatible with the Protocol Buffer wire format.
     34  *
     35  * @author aappleby (a] google.com (Austin Appleby)
     36  */
     37 
     38 goog.provide('jspb.BinaryEncoder');
     39 
     40 goog.require('goog.asserts');
     41 goog.require('jspb.BinaryConstants');
     42 goog.require('jspb.utils');
     43 
     44 
     45 
     46 /**
     47  * BinaryEncoder implements encoders for all the wire types specified in
     48  * https://developers.google.com/protocol-buffers/docs/encoding.
     49  *
     50  * @constructor
     51  * @struct
     52  */
     53 jspb.BinaryEncoder = function() {
     54   /** @private {!Array.<number>} */
     55   this.buffer_ = [];
     56 };
     57 
     58 
     59 /**
     60  * @return {number}
     61  */
     62 jspb.BinaryEncoder.prototype.length = function() {
     63   return this.buffer_.length;
     64 };
     65 
     66 
     67 /**
     68  * @return {!Array.<number>}
     69  */
     70 jspb.BinaryEncoder.prototype.end = function() {
     71   var buffer = this.buffer_;
     72   this.buffer_ = [];
     73   return buffer;
     74 };
     75 
     76 
     77 /**
     78  * Encodes a 64-bit integer in 32:32 split representation into its wire-format
     79  * varint representation and stores it in the buffer.
     80  * @param {number} lowBits The low 32 bits of the int.
     81  * @param {number} highBits The high 32 bits of the int.
     82  */
     83 jspb.BinaryEncoder.prototype.writeSplitVarint64 = function(lowBits, highBits) {
     84   goog.asserts.assert(lowBits == Math.floor(lowBits));
     85   goog.asserts.assert(highBits == Math.floor(highBits));
     86   goog.asserts.assert((lowBits >= 0) &&
     87                       (lowBits < jspb.BinaryConstants.TWO_TO_32));
     88   goog.asserts.assert((highBits >= 0) &&
     89                       (highBits < jspb.BinaryConstants.TWO_TO_32));
     90 
     91   // Break the binary representation into chunks of 7 bits, set the 8th bit
     92   // in each chunk if it's not the final chunk, and append to the result.
     93   while (highBits > 0 || lowBits > 127) {
     94     this.buffer_.push((lowBits & 0x7f) | 0x80);
     95     lowBits = ((lowBits >>> 7) | (highBits << 25)) >>> 0;
     96     highBits = highBits >>> 7;
     97   }
     98   this.buffer_.push(lowBits);
     99 };
    100 
    101 
    102 /**
    103  * Encodes a 32-bit unsigned integer into its wire-format varint representation
    104  * and stores it in the buffer.
    105  * @param {number} value The integer to convert.
    106  */
    107 jspb.BinaryEncoder.prototype.writeUnsignedVarint32 = function(value) {
    108   goog.asserts.assert(value == Math.floor(value));
    109   goog.asserts.assert((value >= 0) &&
    110                       (value < jspb.BinaryConstants.TWO_TO_32));
    111 
    112   while (value > 127) {
    113     this.buffer_.push((value & 0x7f) | 0x80);
    114     value = value >>> 7;
    115   }
    116 
    117   this.buffer_.push(value);
    118 };
    119 
    120 
    121 /**
    122  * Encodes a 32-bit signed integer into its wire-format varint representation
    123  * and stores it in the buffer.
    124  * @param {number} value The integer to convert.
    125  */
    126 jspb.BinaryEncoder.prototype.writeSignedVarint32 = function(value) {
    127   goog.asserts.assert(value == Math.floor(value));
    128   goog.asserts.assert((value >= -jspb.BinaryConstants.TWO_TO_31) &&
    129                       (value < jspb.BinaryConstants.TWO_TO_31));
    130 
    131   // Use the unsigned version if the value is not negative.
    132   if (value >= 0) {
    133     this.writeUnsignedVarint32(value);
    134     return;
    135   }
    136 
    137   // Write nine bytes with a _signed_ right shift so we preserve the sign bit.
    138   for (var i = 0; i < 9; i++) {
    139     this.buffer_.push((value & 0x7f) | 0x80);
    140     value = value >> 7;
    141   }
    142 
    143   // The above loop writes out 63 bits, so the last byte is always the sign bit
    144   // which is always set for negative numbers.
    145   this.buffer_.push(1);
    146 };
    147 
    148 
    149 /**
    150  * Encodes a 64-bit unsigned integer into its wire-format varint representation
    151  * and stores it in the buffer. Integers that are not representable in 64 bits
    152  * will be truncated.
    153  * @param {number} value The integer to convert.
    154  */
    155 jspb.BinaryEncoder.prototype.writeUnsignedVarint64 = function(value) {
    156   goog.asserts.assert(value == Math.floor(value));
    157   goog.asserts.assert((value >= 0) &&
    158                       (value < jspb.BinaryConstants.TWO_TO_64));
    159   jspb.utils.splitInt64(value);
    160   this.writeSplitVarint64(jspb.utils.split64Low,
    161                           jspb.utils.split64High);
    162 };
    163 
    164 
    165 /**
    166  * Encodes a 64-bit signed integer into its wire-format varint representation
    167  * and stores it in the buffer. Integers that are not representable in 64 bits
    168  * will be truncated.
    169  * @param {number} value The integer to convert.
    170  */
    171 jspb.BinaryEncoder.prototype.writeSignedVarint64 = function(value) {
    172   goog.asserts.assert(value == Math.floor(value));
    173   goog.asserts.assert((value >= -jspb.BinaryConstants.TWO_TO_63) &&
    174                       (value < jspb.BinaryConstants.TWO_TO_63));
    175   jspb.utils.splitInt64(value);
    176   this.writeSplitVarint64(jspb.utils.split64Low,
    177                           jspb.utils.split64High);
    178 };
    179 
    180 
    181 /**
    182  * Encodes a JavaScript integer into its wire-format, zigzag-encoded varint
    183  * representation and stores it in the buffer.
    184  * @param {number} value The integer to convert.
    185  */
    186 jspb.BinaryEncoder.prototype.writeZigzagVarint32 = function(value) {
    187   goog.asserts.assert(value == Math.floor(value));
    188   goog.asserts.assert((value >= -jspb.BinaryConstants.TWO_TO_31) &&
    189                       (value < jspb.BinaryConstants.TWO_TO_31));
    190   this.writeUnsignedVarint32(((value << 1) ^ (value >> 31)) >>> 0);
    191 };
    192 
    193 
    194 /**
    195  * Encodes a JavaScript integer into its wire-format, zigzag-encoded varint
    196  * representation and stores it in the buffer. Integers not representable in 64
    197  * bits will be truncated.
    198  * @param {number} value The integer to convert.
    199  */
    200 jspb.BinaryEncoder.prototype.writeZigzagVarint64 = function(value) {
    201   goog.asserts.assert(value == Math.floor(value));
    202   goog.asserts.assert((value >= -jspb.BinaryConstants.TWO_TO_63) &&
    203                       (value < jspb.BinaryConstants.TWO_TO_63));
    204   jspb.utils.splitZigzag64(value);
    205   this.writeSplitVarint64(jspb.utils.split64Low,
    206                           jspb.utils.split64High);
    207 };
    208 
    209 
    210 /**
    211  * Writes a 8-bit unsigned integer to the buffer. Numbers outside the range
    212  * [0,2^8) will be truncated.
    213  * @param {number} value The value to write.
    214  */
    215 jspb.BinaryEncoder.prototype.writeUint8 = function(value) {
    216   goog.asserts.assert(value == Math.floor(value));
    217   goog.asserts.assert((value >= 0) && (value < 256));
    218   this.buffer_.push((value >>> 0) & 0xFF);
    219 };
    220 
    221 
    222 /**
    223  * Writes a 16-bit unsigned integer to the buffer. Numbers outside the
    224  * range [0,2^16) will be truncated.
    225  * @param {number} value The value to write.
    226  */
    227 jspb.BinaryEncoder.prototype.writeUint16 = function(value) {
    228   goog.asserts.assert(value == Math.floor(value));
    229   goog.asserts.assert((value >= 0) && (value < 65536));
    230   this.buffer_.push((value >>> 0) & 0xFF);
    231   this.buffer_.push((value >>> 8) & 0xFF);
    232 };
    233 
    234 
    235 /**
    236  * Writes a 32-bit unsigned integer to the buffer. Numbers outside the
    237  * range [0,2^32) will be truncated.
    238  * @param {number} value The value to write.
    239  */
    240 jspb.BinaryEncoder.prototype.writeUint32 = function(value) {
    241   goog.asserts.assert(value == Math.floor(value));
    242   goog.asserts.assert((value >= 0) &&
    243                       (value < jspb.BinaryConstants.TWO_TO_32));
    244   this.buffer_.push((value >>> 0) & 0xFF);
    245   this.buffer_.push((value >>> 8) & 0xFF);
    246   this.buffer_.push((value >>> 16) & 0xFF);
    247   this.buffer_.push((value >>> 24) & 0xFF);
    248 };
    249 
    250 
    251 /**
    252  * Writes a 64-bit unsigned integer to the buffer. Numbers outside the
    253  * range [0,2^64) will be truncated.
    254  * @param {number} value The value to write.
    255  */
    256 jspb.BinaryEncoder.prototype.writeUint64 = function(value) {
    257   goog.asserts.assert(value == Math.floor(value));
    258   goog.asserts.assert((value >= 0) &&
    259                       (value < jspb.BinaryConstants.TWO_TO_64));
    260   jspb.utils.splitUint64(value);
    261   this.writeUint32(jspb.utils.split64Low);
    262   this.writeUint32(jspb.utils.split64High);
    263 };
    264 
    265 
    266 /**
    267  * Writes a 8-bit integer to the buffer. Numbers outside the range
    268  * [-2^7,2^7) will be truncated.
    269  * @param {number} value The value to write.
    270  */
    271 jspb.BinaryEncoder.prototype.writeInt8 = function(value) {
    272   goog.asserts.assert(value == Math.floor(value));
    273   goog.asserts.assert((value >= -128) && (value < 128));
    274   this.buffer_.push((value >>> 0) & 0xFF);
    275 };
    276 
    277 
    278 /**
    279  * Writes a 16-bit integer to the buffer. Numbers outside the range
    280  * [-2^15,2^15) will be truncated.
    281  * @param {number} value The value to write.
    282  */
    283 jspb.BinaryEncoder.prototype.writeInt16 = function(value) {
    284   goog.asserts.assert(value == Math.floor(value));
    285   goog.asserts.assert((value >= -32768) && (value < 32768));
    286   this.buffer_.push((value >>> 0) & 0xFF);
    287   this.buffer_.push((value >>> 8) & 0xFF);
    288 };
    289 
    290 
    291 /**
    292  * Writes a 32-bit integer to the buffer. Numbers outside the range
    293  * [-2^31,2^31) will be truncated.
    294  * @param {number} value The value to write.
    295  */
    296 jspb.BinaryEncoder.prototype.writeInt32 = function(value) {
    297   goog.asserts.assert(value == Math.floor(value));
    298   goog.asserts.assert((value >= -jspb.BinaryConstants.TWO_TO_31) &&
    299                       (value < jspb.BinaryConstants.TWO_TO_31));
    300   this.buffer_.push((value >>> 0) & 0xFF);
    301   this.buffer_.push((value >>> 8) & 0xFF);
    302   this.buffer_.push((value >>> 16) & 0xFF);
    303   this.buffer_.push((value >>> 24) & 0xFF);
    304 };
    305 
    306 
    307 /**
    308  * Writes a 64-bit integer to the buffer. Numbers outside the range
    309  * [-2^63,2^63) will be truncated.
    310  * @param {number} value The value to write.
    311  */
    312 jspb.BinaryEncoder.prototype.writeInt64 = function(value) {
    313   goog.asserts.assert(value == Math.floor(value));
    314   goog.asserts.assert((value >= -jspb.BinaryConstants.TWO_TO_63) &&
    315                       (value < jspb.BinaryConstants.TWO_TO_63));
    316   jspb.utils.splitInt64(value);
    317   this.writeUint32(jspb.utils.split64Low);
    318   this.writeUint32(jspb.utils.split64High);
    319 };
    320 
    321 
    322 /**
    323  * Writes a single-precision floating point value to the buffer. Numbers
    324  * requiring more than 32 bits of precision will be truncated.
    325  * @param {number} value The value to write.
    326  */
    327 jspb.BinaryEncoder.prototype.writeFloat = function(value) {
    328   goog.asserts.assert((value >= -jspb.BinaryConstants.FLOAT32_MAX) &&
    329                       (value <= jspb.BinaryConstants.FLOAT32_MAX));
    330   jspb.utils.splitFloat32(value);
    331   this.writeUint32(jspb.utils.split64Low);
    332 };
    333 
    334 
    335 /**
    336  * Writes a double-precision floating point value to the buffer. As this is
    337  * the native format used by JavaScript, no precision will be lost.
    338  * @param {number} value The value to write.
    339  */
    340 jspb.BinaryEncoder.prototype.writeDouble = function(value) {
    341   goog.asserts.assert((value >= -jspb.BinaryConstants.FLOAT64_MAX) &&
    342                       (value <= jspb.BinaryConstants.FLOAT64_MAX));
    343   jspb.utils.splitFloat64(value);
    344   this.writeUint32(jspb.utils.split64Low);
    345   this.writeUint32(jspb.utils.split64High);
    346 };
    347 
    348 
    349 /**
    350  * Writes a boolean value to the buffer as a varint.
    351  * @param {boolean} value The value to write.
    352  */
    353 jspb.BinaryEncoder.prototype.writeBool = function(value) {
    354   goog.asserts.assert(goog.isBoolean(value));
    355   this.buffer_.push(value ? 1 : 0);
    356 };
    357 
    358 
    359 /**
    360  * Writes an enum value to the buffer as a varint.
    361  * @param {number} value The value to write.
    362  */
    363 jspb.BinaryEncoder.prototype.writeEnum = function(value) {
    364   goog.asserts.assert(value == Math.floor(value));
    365   goog.asserts.assert((value >= -jspb.BinaryConstants.TWO_TO_31) &&
    366                       (value < jspb.BinaryConstants.TWO_TO_31));
    367   this.writeSignedVarint32(value);
    368 };
    369 
    370 
    371 /**
    372  * Writes an arbitrary byte array to the buffer.
    373  * @param {!Uint8Array} bytes The array of bytes to write.
    374  */
    375 jspb.BinaryEncoder.prototype.writeBytes = function(bytes) {
    376   this.buffer_.push.apply(this.buffer_, bytes);
    377 };
    378 
    379 
    380 /**
    381  * Writes a 64-bit hash string (8 characters @ 8 bits of data each) to the
    382  * buffer as a varint.
    383  * @param {string} hash The hash to write.
    384  */
    385 jspb.BinaryEncoder.prototype.writeVarintHash64 = function(hash) {
    386   jspb.utils.splitHash64(hash);
    387   this.writeSplitVarint64(jspb.utils.split64Low,
    388                           jspb.utils.split64High);
    389 };
    390 
    391 
    392 /**
    393  * Writes a 64-bit hash string (8 characters @ 8 bits of data each) to the
    394  * buffer as a fixed64.
    395  * @param {string} hash The hash to write.
    396  */
    397 jspb.BinaryEncoder.prototype.writeFixedHash64 = function(hash) {
    398   jspb.utils.splitHash64(hash);
    399   this.writeUint32(jspb.utils.split64Low);
    400   this.writeUint32(jspb.utils.split64High);
    401 };
    402 
    403 
    404 /**
    405  * Writes a UTF16 Javascript string to the buffer encoded as UTF8.
    406  * TODO(aappleby): Add support for surrogate pairs, reject unpaired surrogates.
    407  * @param {string} value The string to write.
    408  * @return {number} The number of bytes used to encode the string.
    409  */
    410 jspb.BinaryEncoder.prototype.writeString = function(value) {
    411   var oldLength = this.buffer_.length;
    412 
    413   // UTF16 to UTF8 conversion loop swiped from goog.crypt.stringToUtf8ByteArray.
    414   for (var i = 0; i < value.length; i++) {
    415     var c = value.charCodeAt(i);
    416     if (c < 128) {
    417       this.buffer_.push(c);
    418     } else if (c < 2048) {
    419       this.buffer_.push((c >> 6) | 192);
    420       this.buffer_.push((c & 63) | 128);
    421     } else {
    422       this.buffer_.push((c >> 12) | 224);
    423       this.buffer_.push(((c >> 6) & 63) | 128);
    424       this.buffer_.push((c & 63) | 128);
    425     }
    426   }
    427 
    428   var length = this.buffer_.length - oldLength;
    429   return length;
    430 };
    431