iw6-mod/deps/protobuf/js/experimental/runtime/kernel/writer.js
2024-02-27 01:34:37 -05:00

744 lines
21 KiB
JavaScript

/**
* @fileoverview Implements Writer for writing data as the binary wire format
* bytes array.
*/
goog.module('protobuf.binary.Writer');
const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
const ByteString = goog.require('protobuf.ByteString');
const Int64 = goog.require('protobuf.Int64');
const WireType = goog.require('protobuf.binary.WireType');
const {POLYFILL_TEXT_ENCODING, checkFieldNumber, checkTypeUnsignedInt32, checkWireType} = goog.require('protobuf.internal.checks');
const {concatenateByteArrays} = goog.require('protobuf.binary.uint8arrays');
const {createTag, getTagLength} = goog.require('protobuf.binary.tag');
const {encode} = goog.require('protobuf.binary.textencoding');
/**
* Returns a valid utf-8 encoder function based on TextEncoder if available or
* a polyfill.
* Some of the environments we run in do not have TextEncoder defined.
* TextEncoder is faster than our polyfill so we prefer it over the polyfill.
* @return {function(string):!Uint8Array}
*/
function getEncoderFunction() {
if (goog.global['TextEncoder']) {
const textEncoder = new goog.global['TextEncoder']('utf-8');
return s => s.length === 0 ? new Uint8Array(0) : textEncoder.encode(s);
}
if (POLYFILL_TEXT_ENCODING) {
return encode;
} else {
throw new Error(
'TextEncoder is missing. ' +
'Enable protobuf.defines.POLYFILL_TEXT_ENCODING');
}
}
/** @const {function(string): !Uint8Array} */
const encoderFunction = getEncoderFunction();
/**
* Writer provides methods for encoding all protobuf supported type into a
* binary format bytes array.
* Check https://developers.google.com/protocol-buffers/docs/encoding for binary
* format definition.
* @final
* @package
*/
class Writer {
constructor() {
/**
* Blocks of data that needs to be serialized. After writing all the data,
* the blocks are concatenated into a single Uint8Array.
* @private {!Array<!Uint8Array>}
*/
this.blocks_ = [];
/**
* A buffer for writing varint data (tag number + field number for each
* field, int32, uint32 etc.). Before writing a non-varint data block
* (string, fixed32 etc.), the buffer is appended to the block array as a
* new block, and a new buffer is started.
*
* We could've written each varint as a new block instead of writing
* multiple varints in this buffer. But this will increase the number of
* blocks, and concatenating many small blocks is slower than concatenating
* few large blocks.
*
* TODO: Experiment with writing data in a fixed-length
* Uint8Array instead of using a growing buffer.
*
* @private {!Array<number>}
*/
this.currentBuffer_ = [];
}
/**
* Converts the encoded data into a Uint8Array.
* The writer is also reset.
* @return {!ArrayBuffer}
*/
getAndResetResultBuffer() {
this.closeAndStartNewBuffer_();
const result = concatenateByteArrays(this.blocks_);
this.blocks_ = [];
return result.buffer;
}
/**
* Encodes a (field number, wire type) tuple into a wire-format field header.
* @param {number} fieldNumber
* @param {!WireType} wireType
*/
writeTag(fieldNumber, wireType) {
checkFieldNumber(fieldNumber);
checkWireType(wireType);
const tag = createTag(wireType, fieldNumber);
this.writeUnsignedVarint32_(tag);
}
/**
* Appends the current buffer into the blocks array and starts a new buffer.
* @private
*/
closeAndStartNewBuffer_() {
this.blocks_.push(new Uint8Array(this.currentBuffer_));
this.currentBuffer_ = [];
}
/**
* Encodes a 32-bit integer into its wire-format varint representation and
* stores it in the buffer.
* @param {number} value
* @private
*/
writeUnsignedVarint32_(value) {
checkTypeUnsignedInt32(value);
while (value > 0x7f) {
this.currentBuffer_.push((value & 0x7f) | 0x80);
value = value >>> 7;
}
this.currentBuffer_.push(value);
}
/****************************************************************************
* OPTIONAL METHODS
****************************************************************************/
/**
* Writes a boolean value field to the buffer as a varint.
* @param {boolean} value
* @private
*/
writeBoolValue_(value) {
this.currentBuffer_.push(value ? 1 : 0);
}
/**
* Writes a boolean value field to the buffer as a varint.
* @param {number} fieldNumber
* @param {boolean} value
*/
writeBool(fieldNumber, value) {
this.writeTag(fieldNumber, WireType.VARINT);
this.writeBoolValue_(value);
}
/**
* Writes a bytes value field to the buffer as a length delimited field.
* @param {number} fieldNumber
* @param {!ByteString} value
*/
writeBytes(fieldNumber, value) {
this.writeTag(fieldNumber, WireType.DELIMITED);
const buffer = value.toArrayBuffer();
this.writeUnsignedVarint32_(buffer.byteLength);
this.writeRaw_(buffer);
}
/**
* Writes a double value field to the buffer without tag.
* @param {number} value
* @private
*/
writeDoubleValue_(value) {
const buffer = new ArrayBuffer(8);
const view = new DataView(buffer);
view.setFloat64(0, value, true);
this.writeRaw_(buffer);
}
/**
* Writes a double value field to the buffer.
* @param {number} fieldNumber
* @param {number} value
*/
writeDouble(fieldNumber, value) {
this.writeTag(fieldNumber, WireType.FIXED64);
this.writeDoubleValue_(value);
}
/**
* Writes a fixed32 value field to the buffer without tag.
* @param {number} value
* @private
*/
writeFixed32Value_(value) {
const buffer = new ArrayBuffer(4);
const view = new DataView(buffer);
view.setUint32(0, value, true);
this.writeRaw_(buffer);
}
/**
* Writes a fixed32 value field to the buffer.
* @param {number} fieldNumber
* @param {number} value
*/
writeFixed32(fieldNumber, value) {
this.writeTag(fieldNumber, WireType.FIXED32);
this.writeFixed32Value_(value);
}
/**
* Writes a float value field to the buffer without tag.
* @param {number} value
* @private
*/
writeFloatValue_(value) {
const buffer = new ArrayBuffer(4);
const view = new DataView(buffer);
view.setFloat32(0, value, true);
this.writeRaw_(buffer);
}
/**
* Writes a float value field to the buffer.
* @param {number} fieldNumber
* @param {number} value
*/
writeFloat(fieldNumber, value) {
this.writeTag(fieldNumber, WireType.FIXED32);
this.writeFloatValue_(value);
}
/**
* Writes a int32 value field to the buffer as a varint without tag.
* @param {number} value
* @private
*/
writeInt32Value_(value) {
if (value >= 0) {
this.writeVarint64_(0, value);
} else {
this.writeVarint64_(0xFFFFFFFF, value);
}
}
/**
* Writes a int32 value field to the buffer as a varint.
* @param {number} fieldNumber
* @param {number} value
*/
writeInt32(fieldNumber, value) {
this.writeTag(fieldNumber, WireType.VARINT);
this.writeInt32Value_(value);
}
/**
* Writes a int64 value field to the buffer as a varint.
* @param {number} fieldNumber
* @param {!Int64} value
*/
writeInt64(fieldNumber, value) {
this.writeTag(fieldNumber, WireType.VARINT);
this.writeVarint64_(value.getHighBits(), value.getLowBits());
}
/**
* Writes a sfixed32 value field to the buffer.
* @param {number} value
* @private
*/
writeSfixed32Value_(value) {
const buffer = new ArrayBuffer(4);
const view = new DataView(buffer);
view.setInt32(0, value, true);
this.writeRaw_(buffer);
}
/**
* Writes a sfixed32 value field to the buffer.
* @param {number} fieldNumber
* @param {number} value
*/
writeSfixed32(fieldNumber, value) {
this.writeTag(fieldNumber, WireType.FIXED32);
this.writeSfixed32Value_(value);
}
/**
* Writes a sfixed64 value field to the buffer without tag.
* @param {!Int64} value
* @private
*/
writeSfixed64Value_(value) {
const buffer = new ArrayBuffer(8);
const view = new DataView(buffer);
view.setInt32(0, value.getLowBits(), true);
view.setInt32(4, value.getHighBits(), true);
this.writeRaw_(buffer);
}
/**
* Writes a sfixed64 value field to the buffer.
* @param {number} fieldNumber
* @param {!Int64} value
*/
writeSfixed64(fieldNumber, value) {
this.writeTag(fieldNumber, WireType.FIXED64);
this.writeSfixed64Value_(value);
}
/**
* Writes a sfixed64 value field to the buffer.
* @param {number} fieldNumber
*/
writeStartGroup(fieldNumber) {
this.writeTag(fieldNumber, WireType.START_GROUP);
}
/**
* Writes a sfixed64 value field to the buffer.
* @param {number} fieldNumber
*/
writeEndGroup(fieldNumber) {
this.writeTag(fieldNumber, WireType.END_GROUP);
}
/**
* Writes a uint32 value field to the buffer as a varint without tag.
* @param {number} value
* @private
*/
writeUint32Value_(value) {
this.writeVarint64_(0, value);
}
/**
* Writes a uint32 value field to the buffer as a varint.
* @param {number} fieldNumber
* @param {number} value
*/
writeUint32(fieldNumber, value) {
this.writeTag(fieldNumber, WireType.VARINT);
this.writeUint32Value_(value);
}
/**
* Writes the bits of a 64 bit number to the buffer as a varint.
* @param {number} highBits
* @param {number} lowBits
* @private
*/
writeVarint64_(highBits, lowBits) {
for (let i = 0; i < 28; i = i + 7) {
const shift = lowBits >>> i;
const hasNext = !((shift >>> 7) === 0 && highBits === 0);
const byte = (hasNext ? shift | 0x80 : shift) & 0xFF;
this.currentBuffer_.push(byte);
if (!hasNext) {
return;
}
}
const splitBits = ((lowBits >>> 28) & 0x0F) | ((highBits & 0x07) << 4);
const hasMoreBits = !((highBits >> 3) === 0);
this.currentBuffer_.push(
(hasMoreBits ? splitBits | 0x80 : splitBits) & 0xFF);
if (!hasMoreBits) {
return;
}
for (let i = 3; i < 31; i = i + 7) {
const shift = highBits >>> i;
const hasNext = !((shift >>> 7) === 0);
const byte = (hasNext ? shift | 0x80 : shift) & 0xFF;
this.currentBuffer_.push(byte);
if (!hasNext) {
return;
}
}
this.currentBuffer_.push((highBits >>> 31) & 0x01);
}
/**
* Writes a sint32 value field to the buffer as a varint without tag.
* @param {number} value
* @private
*/
writeSint32Value_(value) {
value = (value << 1) ^ (value >> 31);
this.writeVarint64_(0, value);
}
/**
* Writes a sint32 value field to the buffer as a varint.
* @param {number} fieldNumber
* @param {number} value
*/
writeSint32(fieldNumber, value) {
this.writeTag(fieldNumber, WireType.VARINT);
this.writeSint32Value_(value);
}
/**
* Writes a sint64 value field to the buffer as a varint without tag.
* @param {!Int64} value
* @private
*/
writeSint64Value_(value) {
const highBits = value.getHighBits();
const lowBits = value.getLowBits();
const sign = highBits >> 31;
const encodedLowBits = (lowBits << 1) ^ sign;
const encodedHighBits = ((highBits << 1) | (lowBits >>> 31)) ^ sign;
this.writeVarint64_(encodedHighBits, encodedLowBits);
}
/**
* Writes a sint64 value field to the buffer as a varint.
* @param {number} fieldNumber
* @param {!Int64} value
*/
writeSint64(fieldNumber, value) {
this.writeTag(fieldNumber, WireType.VARINT);
this.writeSint64Value_(value);
}
/**
* Writes a string value field to the buffer as a varint.
* @param {number} fieldNumber
* @param {string} value
*/
writeString(fieldNumber, value) {
this.writeTag(fieldNumber, WireType.DELIMITED);
const array = encoderFunction(value);
this.writeUnsignedVarint32_(array.length);
this.closeAndStartNewBuffer_();
this.blocks_.push(array);
}
/**
* Writes raw bytes to the buffer.
* @param {!ArrayBuffer} arrayBuffer
* @private
*/
writeRaw_(arrayBuffer) {
this.closeAndStartNewBuffer_();
this.blocks_.push(new Uint8Array(arrayBuffer));
}
/**
* Writes raw bytes to the buffer.
* @param {!BufferDecoder} bufferDecoder
* @param {number} start
* @param {!WireType} wireType
* @param {number} fieldNumber
* @package
*/
writeBufferDecoder(bufferDecoder, start, wireType, fieldNumber) {
this.closeAndStartNewBuffer_();
const dataLength =
getTagLength(bufferDecoder, start, wireType, fieldNumber);
this.blocks_.push(
bufferDecoder.subBufferDecoder(start, dataLength).asUint8Array());
}
/**
* Write the whole bytes as a length delimited field.
* @param {number} fieldNumber
* @param {!ArrayBuffer} arrayBuffer
*/
writeDelimited(fieldNumber, arrayBuffer) {
this.writeTag(fieldNumber, WireType.DELIMITED);
this.writeUnsignedVarint32_(arrayBuffer.byteLength);
this.writeRaw_(arrayBuffer);
}
/****************************************************************************
* REPEATED METHODS
****************************************************************************/
/**
* Writes repeated boolean values to the buffer as unpacked varints.
* @param {number} fieldNumber
* @param {!Array<boolean>} values
*/
writeRepeatedBool(fieldNumber, values) {
values.forEach(val => this.writeBool(fieldNumber, val));
}
/**
* Writes repeated boolean values to the buffer as packed varints.
* @param {number} fieldNumber
* @param {!Array<boolean>} values
*/
writePackedBool(fieldNumber, values) {
this.writeFixedPacked_(
fieldNumber, values, val => this.writeBoolValue_(val), 1);
}
/**
* Writes repeated double values to the buffer as unpacked fixed64.
* @param {number} fieldNumber
* @param {!Array<number>} values
*/
writeRepeatedDouble(fieldNumber, values) {
values.forEach(val => this.writeDouble(fieldNumber, val));
}
/**
* Writes repeated double values to the buffer as packed fixed64.
* @param {number} fieldNumber
* @param {!Array<number>} values
*/
writePackedDouble(fieldNumber, values) {
this.writeFixedPacked_(
fieldNumber, values, val => this.writeDoubleValue_(val), 8);
}
/**
* Writes repeated fixed32 values to the buffer as unpacked fixed32.
* @param {number} fieldNumber
* @param {!Array<number>} values
*/
writeRepeatedFixed32(fieldNumber, values) {
values.forEach(val => this.writeFixed32(fieldNumber, val));
}
/**
* Writes repeated fixed32 values to the buffer as packed fixed32.
* @param {number} fieldNumber
* @param {!Array<number>} values
*/
writePackedFixed32(fieldNumber, values) {
this.writeFixedPacked_(
fieldNumber, values, val => this.writeFixed32Value_(val), 4);
}
/**
* Writes repeated float values to the buffer as unpacked fixed64.
* @param {number} fieldNumber
* @param {!Array<number>} values
*/
writeRepeatedFloat(fieldNumber, values) {
values.forEach(val => this.writeFloat(fieldNumber, val));
}
/**
* Writes repeated float values to the buffer as packed fixed64.
* @param {number} fieldNumber
* @param {!Array<number>} values
*/
writePackedFloat(fieldNumber, values) {
this.writeFixedPacked_(
fieldNumber, values, val => this.writeFloatValue_(val), 4);
}
/**
* Writes repeated int32 values to the buffer as unpacked int32.
* @param {number} fieldNumber
* @param {!Array<number>} values
*/
writeRepeatedInt32(fieldNumber, values) {
values.forEach(val => this.writeInt32(fieldNumber, val));
}
/**
* Writes repeated int32 values to the buffer as packed int32.
* @param {number} fieldNumber
* @param {!Array<number>} values
*/
writePackedInt32(fieldNumber, values) {
this.writeVariablePacked_(
fieldNumber, values, (writer, val) => writer.writeInt32Value_(val));
}
/**
* Writes repeated int64 values to the buffer as unpacked varint.
* @param {number} fieldNumber
* @param {!Array<!Int64>} values
*/
writeRepeatedInt64(fieldNumber, values) {
values.forEach(val => this.writeInt64(fieldNumber, val));
}
/**
* Writes repeated int64 values to the buffer as packed varint.
* @param {number} fieldNumber
* @param {!Array<!Int64>} values
*/
writePackedInt64(fieldNumber, values) {
this.writeVariablePacked_(
fieldNumber, values,
(writer, val) =>
writer.writeVarint64_(val.getHighBits(), val.getLowBits()));
}
/**
* Writes repeated sfixed32 values to the buffer as unpacked fixed32.
* @param {number} fieldNumber
* @param {!Array<number>} values
*/
writeRepeatedSfixed32(fieldNumber, values) {
values.forEach(val => this.writeSfixed32(fieldNumber, val));
}
/**
* Writes repeated sfixed32 values to the buffer as packed fixed32.
* @param {number} fieldNumber
* @param {!Array<number>} values
*/
writePackedSfixed32(fieldNumber, values) {
this.writeFixedPacked_(
fieldNumber, values, val => this.writeSfixed32Value_(val), 4);
}
/**
* Writes repeated sfixed64 values to the buffer as unpacked fixed64.
* @param {number} fieldNumber
* @param {!Array<!Int64>} values
*/
writeRepeatedSfixed64(fieldNumber, values) {
values.forEach(val => this.writeSfixed64(fieldNumber, val));
}
/**
* Writes repeated sfixed64 values to the buffer as packed fixed64.
* @param {number} fieldNumber
* @param {!Array<!Int64>} values
*/
writePackedSfixed64(fieldNumber, values) {
this.writeFixedPacked_(
fieldNumber, values, val => this.writeSfixed64Value_(val), 8);
}
/**
* Writes repeated sint32 values to the buffer as unpacked sint32.
* @param {number} fieldNumber
* @param {!Array<number>} values
*/
writeRepeatedSint32(fieldNumber, values) {
values.forEach(val => this.writeSint32(fieldNumber, val));
}
/**
* Writes repeated sint32 values to the buffer as packed sint32.
* @param {number} fieldNumber
* @param {!Array<number>} values
*/
writePackedSint32(fieldNumber, values) {
this.writeVariablePacked_(
fieldNumber, values, (writer, val) => writer.writeSint32Value_(val));
}
/**
* Writes repeated sint64 values to the buffer as unpacked varint.
* @param {number} fieldNumber
* @param {!Array<!Int64>} values
*/
writeRepeatedSint64(fieldNumber, values) {
values.forEach(val => this.writeSint64(fieldNumber, val));
}
/**
* Writes repeated sint64 values to the buffer as packed varint.
* @param {number} fieldNumber
* @param {!Array<!Int64>} values
*/
writePackedSint64(fieldNumber, values) {
this.writeVariablePacked_(
fieldNumber, values, (writer, val) => writer.writeSint64Value_(val));
}
/**
* Writes repeated uint32 values to the buffer as unpacked uint32.
* @param {number} fieldNumber
* @param {!Array<number>} values
*/
writeRepeatedUint32(fieldNumber, values) {
values.forEach(val => this.writeUint32(fieldNumber, val));
}
/**
* Writes repeated uint32 values to the buffer as packed uint32.
* @param {number} fieldNumber
* @param {!Array<number>} values
*/
writePackedUint32(fieldNumber, values) {
this.writeVariablePacked_(
fieldNumber, values, (writer, val) => writer.writeUint32Value_(val));
}
/**
* Writes repeated bytes values to the buffer.
* @param {number} fieldNumber
* @param {!Array<!ByteString>} values
*/
writeRepeatedBytes(fieldNumber, values) {
values.forEach(val => this.writeBytes(fieldNumber, val));
}
/**
* Writes packed fields with fixed length.
* @param {number} fieldNumber
* @param {!Array<T>} values
* @param {function(T)} valueWriter
* @param {number} entitySize
* @template T
* @private
*/
writeFixedPacked_(fieldNumber, values, valueWriter, entitySize) {
if (values.length === 0) {
return;
}
this.writeTag(fieldNumber, WireType.DELIMITED);
this.writeUnsignedVarint32_(values.length * entitySize);
this.closeAndStartNewBuffer_();
values.forEach(value => valueWriter(value));
}
/**
* Writes packed fields with variable length.
* @param {number} fieldNumber
* @param {!Array<T>} values
* @param {function(!Writer, T)} valueWriter
* @template T
* @private
*/
writeVariablePacked_(fieldNumber, values, valueWriter) {
if (values.length === 0) {
return;
}
const writer = new Writer();
values.forEach(val => valueWriter(writer, val));
const bytes = writer.getAndResetResultBuffer();
this.writeDelimited(fieldNumber, bytes);
}
/**
* Writes repeated string values to the buffer.
* @param {number} fieldNumber
* @param {!Array<string>} values
*/
writeRepeatedString(fieldNumber, values) {
values.forEach(val => this.writeString(fieldNumber, val));
}
}
exports = Writer;