blob: 4bc849f947ffff0dc426b1536caacc0acf3a28c7 [file] [log] [blame] [edit]
/**
* @fileoverview BinaryEncode defines methods for encoding Javascript values
* into arrays of bytes compatible with the Protocol Buffer wire format.
*
* @author [email protected] (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};