| /** | |
| * @fileoverview BinaryEncode defines methods for encoding Javascript values | |
| * into arrays of bytes compatible with the Protocol Buffer wire format. | |
| * | |
| * @author aappleby@google.com (Austin Appleby) | |
| */ | |
| goog.module('jspb.binary.encoder'); | |
| goog.module.declareLegacyNamespace(); | |
| const BinaryConstants = goog.require('jspb.BinaryConstants'); | |
| const asserts = goog.require('goog.asserts'); | |
| const utils = goog.require('jspb.utils'); | |
| // The maximum number of bytes to push onto `buffer_` at a time, limited to | |
| // prevent stack overflow errors. | |
| const MAX_PUSH = 8192; | |
| /** | |
| * BinaryEncoder implements encoders for all the wire types specified in | |
| * https://developers.google.com/protocol-buffers/docs/encoding. | |
| */ | |
| class BinaryEncoder { | |
| constructor() { | |
| /** @private {!Array<number>} */ | |
| this.buffer_ = []; | |
| } | |
| /** | |
| * @return {number} | |
| */ | |
| length() { | |
| return this.buffer_.length; | |
| } | |
| /** | |
| * @return {!Array<number>} | |
| */ | |
| end() { | |
| const buffer = this.buffer_; | |
| this.buffer_ = []; | |
| return buffer; | |
| } | |
| /** | |
| * Encodes a 64-bit integer in 32:32 split representation into its wire-format | |
| * varint representation and stores it in the buffer. | |
| * @param {number} lowBits The low 32 bits of the int. | |
| * @param {number} highBits The high 32 bits of the int. | |
| */ | |
| writeSplitVarint64(lowBits, highBits) { | |
| asserts.assert(lowBits == Math.floor(lowBits)); | |
| asserts.assert(highBits == Math.floor(highBits)); | |
| asserts.assert((lowBits >= 0) && (lowBits < BinaryConstants.TWO_TO_32)); | |
| asserts.assert((highBits >= 0) && (highBits < BinaryConstants.TWO_TO_32)); | |
| // Break the binary representation into chunks of 7 bits, set the 8th bit | |
| // in each chunk if it's not the final chunk, and append to the result. | |
| while (highBits > 0 || lowBits > 127) { | |
| this.buffer_.push((lowBits & 0x7f) | 0x80); | |
| lowBits = ((lowBits >>> 7) | (highBits << 25)) >>> 0; | |
| highBits = highBits >>> 7; | |
| } | |
| this.buffer_.push(lowBits); | |
| } | |
| /** | |
| * Encodes a 64-bit integer in 32:32 split representation into its wire-format | |
| * fixed representation and stores it in the buffer. | |
| * @param {number} lowBits The low 32 bits of the int. | |
| * @param {number} highBits The high 32 bits of the int. | |
| */ | |
| writeSplitFixed64(lowBits, highBits) { | |
| asserts.assert(lowBits == Math.floor(lowBits)); | |
| asserts.assert(highBits == Math.floor(highBits)); | |
| asserts.assert((lowBits >= 0) && (lowBits < BinaryConstants.TWO_TO_32)); | |
| asserts.assert((highBits >= 0) && (highBits < BinaryConstants.TWO_TO_32)); | |
| this.writeUint32(lowBits); | |
| this.writeUint32(highBits); | |
| } | |
| /** | |
| * Encodes a 64-bit integer in 32:32 split representation into its wire-format | |
| * a zigzag varint representation and stores it in the buffer. | |
| * @param {number} lowBits The low 32 bits of the int. | |
| * @param {number} highBits The high 32 bits of the int. | |
| */ | |
| writeSplitZigzagVarint64(lowBits, highBits) { | |
| utils.toZigzag64(lowBits, highBits, (lo, hi) => { | |
| this.writeSplitVarint64(lo >>> 0, hi >>> 0); | |
| }); | |
| } | |
| /** | |
| * Encodes a 32-bit unsigned integer into its wire-format varint | |
| * representation and stores it in the buffer. | |
| * @param {number} value The integer to convert. | |
| */ | |
| writeUnsignedVarint32(value) { | |
| asserts.assert(value == Math.floor(value)); | |
| asserts.assert((value >= 0) && (value < BinaryConstants.TWO_TO_32)); | |
| while (value > 127) { | |
| this.buffer_.push((value & 0x7f) | 0x80); | |
| value = value >>> 7; | |
| } | |
| this.buffer_.push(value); | |
| } | |
| /** | |
| * Encodes a 32-bit signed integer into its wire-format varint representation | |
| * and stores it in the buffer. | |
| * @param {number} value The integer to convert. | |
| */ | |
| writeSignedVarint32(value) { | |
| asserts.assert(value == Math.floor(value)); | |
| asserts.assert( | |
| (value >= -BinaryConstants.TWO_TO_31) && | |
| (value < BinaryConstants.TWO_TO_31)); | |
| // Use the unsigned version if the value is not negative. | |
| if (value >= 0) { | |
| this.writeUnsignedVarint32(value); | |
| return; | |
| } | |
| // Write nine bytes with a _signed_ right shift so we preserve the sign bit. | |
| for (let i = 0; i < 9; i++) { | |
| this.buffer_.push((value & 0x7f) | 0x80); | |
| value = value >> 7; | |
| } | |
| // The above loop writes out 63 bits, so the last byte is always the sign | |
| // bit which is always set for negative numbers. | |
| this.buffer_.push(1); | |
| } | |
| /** | |
| * Encodes a 64-bit unsigned integer into its wire-format varint | |
| * representation and stores it in the buffer. Integers that are not | |
| * representable in 64 bits will be truncated. | |
| * @param {number} value The integer to convert. | |
| */ | |
| writeUnsignedVarint64(value) { | |
| asserts.assert(value == Math.floor(value)); | |
| asserts.assert((value >= 0) && (value < BinaryConstants.TWO_TO_64)); | |
| utils.splitInt64(value); | |
| this.writeSplitVarint64(utils.getSplit64Low(), utils.getSplit64High()); | |
| } | |
| /** | |
| * Encodes a 64-bit signed integer into its wire-format varint representation | |
| * and stores it in the buffer. Integers that are not representable in 64 bits | |
| * will be truncated. | |
| * @param {number} value The integer to convert. | |
| */ | |
| writeSignedVarint64(value) { | |
| asserts.assert(value == Math.floor(value)); | |
| asserts.assert( | |
| (value >= -BinaryConstants.TWO_TO_63) && | |
| (value < BinaryConstants.TWO_TO_63)); | |
| utils.splitInt64(value); | |
| this.writeSplitVarint64(utils.getSplit64Low(), utils.getSplit64High()); | |
| } | |
| /** | |
| * Encodes a JavaScript integer into its wire-format, zigzag-encoded varint | |
| * representation and stores it in the buffer. | |
| * @param {number} value The integer to convert. | |
| */ | |
| writeZigzagVarint32(value) { | |
| asserts.assert(value == Math.floor(value)); | |
| asserts.assert( | |
| (value >= -BinaryConstants.TWO_TO_31) && | |
| (value < BinaryConstants.TWO_TO_31)); | |
| this.writeUnsignedVarint32(utils.toZigzag32(value)); | |
| } | |
| /** | |
| * Encodes a JavaScript integer into its wire-format, zigzag-encoded varint | |
| * representation and stores it in the buffer. Integers not representable in | |
| * 64 bits will be truncated. | |
| * @param {number} value The integer to convert. | |
| */ | |
| writeZigzagVarint64(value) { | |
| asserts.assert(value == Math.floor(value)); | |
| asserts.assert( | |
| (value >= -BinaryConstants.TWO_TO_63) && | |
| (value < BinaryConstants.TWO_TO_63)); | |
| utils.splitZigzag64(value); | |
| this.writeSplitVarint64(utils.getSplit64Low(), utils.getSplit64High()); | |
| } | |
| /** | |
| * Encodes a JavaScript decimal string into its wire-format, zigzag-encoded | |
| * varint representation and stores it in the buffer. Integers not | |
| * representable in 64 bits will be truncated. | |
| * @param {string} value The integer to convert. | |
| */ | |
| writeZigzagVarint64String(value) { | |
| utils.splitDecimalString(value); | |
| utils.toZigzag64( | |
| utils.getSplit64Low(), utils.getSplit64High(), (lo, hi) => { | |
| this.writeSplitVarint64(lo >>> 0, hi >>> 0); | |
| }); | |
| } | |
| /** | |
| * Writes an 8-bit unsigned integer to the buffer. Numbers outside the range | |
| * [0,2^8) will be truncated. | |
| * @param {number} value The value to write. | |
| */ | |
| writeUint8(value) { | |
| asserts.assert(value == Math.floor(value)); | |
| asserts.assert((value >= 0) && (value < 256)); | |
| this.buffer_.push((value >>> 0) & 0xFF); | |
| } | |
| /** | |
| * Writes a 16-bit unsigned integer to the buffer. Numbers outside the | |
| * range [0,2^16) will be truncated. | |
| * @param {number} value The value to write. | |
| */ | |
| writeUint16(value) { | |
| asserts.assert(value == Math.floor(value)); | |
| asserts.assert((value >= 0) && (value < 65536)); | |
| this.buffer_.push((value >>> 0) & 0xFF); | |
| this.buffer_.push((value >>> 8) & 0xFF); | |
| } | |
| /** | |
| * Writes a 32-bit unsigned integer to the buffer. Numbers outside the | |
| * range [0,2^32) will be truncated. | |
| * @param {number} value The value to write. | |
| */ | |
| writeUint32(value) { | |
| asserts.assert(value == Math.floor(value)); | |
| asserts.assert((value >= 0) && (value < BinaryConstants.TWO_TO_32)); | |
| this.buffer_.push((value >>> 0) & 0xFF); | |
| this.buffer_.push((value >>> 8) & 0xFF); | |
| this.buffer_.push((value >>> 16) & 0xFF); | |
| this.buffer_.push((value >>> 24) & 0xFF); | |
| } | |
| /** | |
| * Writes a 64-bit unsigned integer to the buffer. Numbers outside the | |
| * range [0,2^64) will be truncated. | |
| * @param {number} value The value to write. | |
| */ | |
| writeUint64(value) { | |
| asserts.assert(value == Math.floor(value)); | |
| asserts.assert((value >= 0) && (value < BinaryConstants.TWO_TO_64)); | |
| utils.splitUint64(value); | |
| this.writeUint32(utils.getSplit64Low()); | |
| this.writeUint32(utils.getSplit64High()); | |
| } | |
| /** | |
| * Writes an 8-bit integer to the buffer. Numbers outside the range | |
| * [-2^7,2^7) will be truncated. | |
| * @param {number} value The value to write. | |
| */ | |
| writeInt8(value) { | |
| asserts.assert(value == Math.floor(value)); | |
| asserts.assert((value >= -128) && (value < 128)); | |
| this.buffer_.push((value >>> 0) & 0xFF); | |
| } | |
| /** | |
| * Writes a 16-bit integer to the buffer. Numbers outside the range | |
| * [-2^15,2^15) will be truncated. | |
| * @param {number} value The value to write. | |
| */ | |
| writeInt16(value) { | |
| asserts.assert(value == Math.floor(value)); | |
| asserts.assert((value >= -32768) && (value < 32768)); | |
| this.buffer_.push((value >>> 0) & 0xFF); | |
| this.buffer_.push((value >>> 8) & 0xFF); | |
| } | |
| /** | |
| * Writes a 32-bit integer to the buffer. Numbers outside the range | |
| * [-2^31,2^31) will be truncated. | |
| * @param {number} value The value to write. | |
| */ | |
| writeInt32(value) { | |
| asserts.assert(value == Math.floor(value)); | |
| asserts.assert( | |
| (value >= -BinaryConstants.TWO_TO_31) && | |
| (value < BinaryConstants.TWO_TO_31)); | |
| this.buffer_.push((value >>> 0) & 0xFF); | |
| this.buffer_.push((value >>> 8) & 0xFF); | |
| this.buffer_.push((value >>> 16) & 0xFF); | |
| this.buffer_.push((value >>> 24) & 0xFF); | |
| } | |
| /** | |
| * Writes a 64-bit integer to the buffer. Numbers outside the range | |
| * [-2^63,2^63) will be truncated. | |
| * @param {number} value The value to write. | |
| */ | |
| writeInt64(value) { | |
| asserts.assert(value == Math.floor(value)); | |
| asserts.assert( | |
| (value >= -BinaryConstants.TWO_TO_63) && | |
| (value < BinaryConstants.TWO_TO_63)); | |
| utils.splitInt64(value); | |
| this.writeSplitFixed64(utils.getSplit64Low(), utils.getSplit64High()); | |
| } | |
| /** | |
| * Writes a single-precision floating point value to the buffer. Numbers | |
| * requiring more than 32 bits of precision will be truncated. | |
| * @param {number|string} value The value to write, accepts | |
| * 'Infinity'/'-Infinity'/'NaN' for JSPB wire format compatibility. | |
| */ | |
| writeFloat(value) { | |
| asserts.assert( | |
| // Explicitly using == to accept strings | |
| (value == Infinity || value == -Infinity || isNaN(value) || | |
| (typeof value === 'number' && | |
| (value >= -BinaryConstants.FLOAT32_MAX) && | |
| (value <= BinaryConstants.FLOAT32_MAX)))); | |
| utils.splitFloat32(value); | |
| this.writeUint32(utils.getSplit64Low()); | |
| } | |
| /** | |
| * Writes a double-precision floating point value to the buffer. As this is | |
| * the native format used by JavaScript, no precision will be lost. | |
| * @param {number|string} value The value to write, accepts | |
| * 'Infinity'/'-Infinity'/'NaN' for JSPB wire format compatibility. | |
| */ | |
| writeDouble(value) { | |
| asserts.assert( | |
| typeof value === 'number' || value === 'Infinity' || | |
| value === '-Infinity' || value === 'NaN'); | |
| utils.splitFloat64(value); | |
| this.writeUint32(utils.getSplit64Low()); | |
| this.writeUint32(utils.getSplit64High()); | |
| } | |
| /** | |
| * Writes a boolean value to the buffer as a varint. We allow numbers as input | |
| * because the JSPB code generator uses 0/1 instead of true/false to save | |
| * space in the string representation of the proto. | |
| * @param {boolean|number} value The value to write. | |
| */ | |
| writeBool(value) { | |
| asserts.assert(typeof value === 'boolean' || typeof value === 'number'); | |
| this.buffer_.push(value ? 1 : 0); | |
| } | |
| /** | |
| * Writes an enum value to the buffer as a varint. | |
| * @param {number} value The value to write. | |
| */ | |
| writeEnum(value) { | |
| asserts.assert(value == Math.floor(value)); | |
| asserts.assert( | |
| (value >= -BinaryConstants.TWO_TO_31) && | |
| (value < BinaryConstants.TWO_TO_31)); | |
| this.writeSignedVarint32(value); | |
| } | |
| /** | |
| * Writes a byte array to our buffer. | |
| * @param {!Uint8Array} bytes The array of bytes to write. | |
| */ | |
| writeBytes(bytes) { | |
| // avoid a stackoverflow on large arrays. | |
| while (bytes.length > MAX_PUSH) { | |
| Array.prototype.push.apply(this.buffer_, bytes.subarray(0, MAX_PUSH)); | |
| bytes = bytes.subarray(MAX_PUSH); | |
| } | |
| Array.prototype.push.apply(this.buffer_, bytes); | |
| } | |
| } | |
| exports = {BinaryEncoder}; |