• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * @fileoverview Implements Writer for writing data as the binary wire format
3 * bytes array.
4 */
5goog.module('protobuf.binary.Writer');
6
7const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
8const ByteString = goog.require('protobuf.ByteString');
9const Int64 = goog.require('protobuf.Int64');
10const WireType = goog.require('protobuf.binary.WireType');
11const {POLYFILL_TEXT_ENCODING, checkFieldNumber, checkTypeUnsignedInt32, checkWireType} = goog.require('protobuf.internal.checks');
12const {concatenateByteArrays} = goog.require('protobuf.binary.uint8arrays');
13const {createTag, getTagLength} = goog.require('protobuf.binary.tag');
14const {encode} = goog.require('protobuf.binary.textencoding');
15
16/**
17 * Returns a valid utf-8 encoder function based on TextEncoder if available or
18 * a polyfill.
19 * Some of the environments we run in do not have TextEncoder defined.
20 * TextEncoder is faster than our polyfill so we prefer it over the polyfill.
21 * @return {function(string):!Uint8Array}
22 */
23function getEncoderFunction() {
24  if (goog.global['TextEncoder']) {
25    const textEncoder = new goog.global['TextEncoder']('utf-8');
26    return s => s.length === 0 ? new Uint8Array(0) : textEncoder.encode(s);
27  }
28  if (POLYFILL_TEXT_ENCODING) {
29    return encode;
30  } else {
31    throw new Error(
32        'TextEncoder is missing. ' +
33        'Enable protobuf.defines.POLYFILL_TEXT_ENCODING');
34  }
35}
36
37/** @const {function(string): !Uint8Array} */
38const encoderFunction = getEncoderFunction();
39
40/**
41 * Writer provides methods for encoding all protobuf supported type into a
42 * binary format bytes array.
43 * Check https://developers.google.com/protocol-buffers/docs/encoding for binary
44 * format definition.
45 * @final
46 * @package
47 */
48class Writer {
49  constructor() {
50    /**
51     * Blocks of data that needs to be serialized. After writing all the data,
52     * the blocks are concatenated into a single Uint8Array.
53     * @private {!Array<!Uint8Array>}
54     */
55    this.blocks_ = [];
56
57    /**
58     * A buffer for writing varint data (tag number + field number for each
59     * field, int32, uint32 etc.). Before writing a non-varint data block
60     * (string, fixed32 etc.), the buffer is appended to the block array as a
61     * new block, and a new buffer is started.
62     *
63     * We could've written each varint as a new block instead of writing
64     * multiple varints in this buffer. But this will increase the number of
65     * blocks, and concatenating many small blocks is slower than concatenating
66     * few large blocks.
67     *
68     * TODO: Experiment with writing data in a fixed-length
69     * Uint8Array instead of using a growing buffer.
70     *
71     * @private {!Array<number>}
72     */
73    this.currentBuffer_ = [];
74  }
75
76  /**
77   * Converts the encoded data into a Uint8Array.
78   * The writer is also reset.
79   * @return {!ArrayBuffer}
80   */
81  getAndResetResultBuffer() {
82    this.closeAndStartNewBuffer_();
83    const result = concatenateByteArrays(this.blocks_);
84    this.blocks_ = [];
85    return result.buffer;
86  }
87
88  /**
89   * Encodes a (field number, wire type) tuple into a wire-format field header.
90   * @param {number} fieldNumber
91   * @param {!WireType} wireType
92   */
93  writeTag(fieldNumber, wireType) {
94    checkFieldNumber(fieldNumber);
95    checkWireType(wireType);
96    const tag = createTag(wireType, fieldNumber);
97    this.writeUnsignedVarint32_(tag);
98  }
99
100  /**
101   * Appends the current buffer into the blocks array and starts a new buffer.
102   * @private
103   */
104  closeAndStartNewBuffer_() {
105    this.blocks_.push(new Uint8Array(this.currentBuffer_));
106    this.currentBuffer_ = [];
107  }
108
109  /**
110   * Encodes a 32-bit integer into its wire-format varint representation and
111   * stores it in the buffer.
112   * @param {number} value
113   * @private
114   */
115  writeUnsignedVarint32_(value) {
116    checkTypeUnsignedInt32(value);
117    while (value > 0x7f) {
118      this.currentBuffer_.push((value & 0x7f) | 0x80);
119      value = value >>> 7;
120    }
121    this.currentBuffer_.push(value);
122  }
123
124  /****************************************************************************
125   *                        OPTIONAL METHODS
126   ****************************************************************************/
127
128  /**
129   * Writes a boolean value field to the buffer as a varint.
130   * @param {boolean} value
131   * @private
132   */
133  writeBoolValue_(value) {
134    this.currentBuffer_.push(value ? 1 : 0);
135  }
136
137  /**
138   * Writes a boolean value field to the buffer as a varint.
139   * @param {number} fieldNumber
140   * @param {boolean} value
141   */
142  writeBool(fieldNumber, value) {
143    this.writeTag(fieldNumber, WireType.VARINT);
144    this.writeBoolValue_(value);
145  }
146
147  /**
148   * Writes a bytes value field to the buffer as a length delimited field.
149   * @param {number} fieldNumber
150   * @param {!ByteString} value
151   */
152  writeBytes(fieldNumber, value) {
153    this.writeTag(fieldNumber, WireType.DELIMITED);
154    const buffer = value.toArrayBuffer();
155    this.writeUnsignedVarint32_(buffer.byteLength);
156    this.writeRaw_(buffer);
157  }
158
159  /**
160   * Writes a double value field to the buffer without tag.
161   * @param {number} value
162   * @private
163   */
164  writeDoubleValue_(value) {
165    const buffer = new ArrayBuffer(8);
166    const view = new DataView(buffer);
167    view.setFloat64(0, value, true);
168    this.writeRaw_(buffer);
169  }
170
171  /**
172   * Writes a double value field to the buffer.
173   * @param {number} fieldNumber
174   * @param {number} value
175   */
176  writeDouble(fieldNumber, value) {
177    this.writeTag(fieldNumber, WireType.FIXED64);
178    this.writeDoubleValue_(value);
179  }
180
181  /**
182   * Writes a fixed32 value field to the buffer without tag.
183   * @param {number} value
184   * @private
185   */
186  writeFixed32Value_(value) {
187    const buffer = new ArrayBuffer(4);
188    const view = new DataView(buffer);
189    view.setUint32(0, value, true);
190    this.writeRaw_(buffer);
191  }
192
193  /**
194   * Writes a fixed32 value field to the buffer.
195   * @param {number} fieldNumber
196   * @param {number} value
197   */
198  writeFixed32(fieldNumber, value) {
199    this.writeTag(fieldNumber, WireType.FIXED32);
200    this.writeFixed32Value_(value);
201  }
202
203  /**
204   * Writes a float value field to the buffer without tag.
205   * @param {number} value
206   * @private
207   */
208  writeFloatValue_(value) {
209    const buffer = new ArrayBuffer(4);
210    const view = new DataView(buffer);
211    view.setFloat32(0, value, true);
212    this.writeRaw_(buffer);
213  }
214
215  /**
216   * Writes a float value field to the buffer.
217   * @param {number} fieldNumber
218   * @param {number} value
219   */
220  writeFloat(fieldNumber, value) {
221    this.writeTag(fieldNumber, WireType.FIXED32);
222    this.writeFloatValue_(value);
223  }
224
225  /**
226   * Writes a int32 value field to the buffer as a varint without tag.
227   * @param {number} value
228   * @private
229   */
230  writeInt32Value_(value) {
231    if (value >= 0) {
232      this.writeVarint64_(0, value);
233    } else {
234      this.writeVarint64_(0xFFFFFFFF, value);
235    }
236  }
237
238  /**
239   * Writes a int32 value field to the buffer as a varint.
240   * @param {number} fieldNumber
241   * @param {number} value
242   */
243  writeInt32(fieldNumber, value) {
244    this.writeTag(fieldNumber, WireType.VARINT);
245    this.writeInt32Value_(value);
246  }
247
248  /**
249   * Writes a int64 value field to the buffer as a varint.
250   * @param {number} fieldNumber
251   * @param {!Int64} value
252   */
253  writeInt64(fieldNumber, value) {
254    this.writeTag(fieldNumber, WireType.VARINT);
255    this.writeVarint64_(value.getHighBits(), value.getLowBits());
256  }
257
258  /**
259   * Writes a sfixed32 value field to the buffer.
260   * @param {number} value
261   * @private
262   */
263  writeSfixed32Value_(value) {
264    const buffer = new ArrayBuffer(4);
265    const view = new DataView(buffer);
266    view.setInt32(0, value, true);
267    this.writeRaw_(buffer);
268  }
269
270  /**
271   * Writes a sfixed32 value field to the buffer.
272   * @param {number} fieldNumber
273   * @param {number} value
274   */
275  writeSfixed32(fieldNumber, value) {
276    this.writeTag(fieldNumber, WireType.FIXED32);
277    this.writeSfixed32Value_(value);
278  }
279
280  /**
281   * Writes a sfixed64 value field to the buffer without tag.
282   * @param {!Int64} value
283   * @private
284   */
285  writeSfixed64Value_(value) {
286    const buffer = new ArrayBuffer(8);
287    const view = new DataView(buffer);
288    view.setInt32(0, value.getLowBits(), true);
289    view.setInt32(4, value.getHighBits(), true);
290    this.writeRaw_(buffer);
291  }
292
293  /**
294   * Writes a sfixed64 value field to the buffer.
295   * @param {number} fieldNumber
296   * @param {!Int64} value
297   */
298  writeSfixed64(fieldNumber, value) {
299    this.writeTag(fieldNumber, WireType.FIXED64);
300    this.writeSfixed64Value_(value);
301  }
302
303  /**
304   * Writes a sfixed64 value field to the buffer.
305   * @param {number} fieldNumber
306   */
307  writeStartGroup(fieldNumber) {
308    this.writeTag(fieldNumber, WireType.START_GROUP);
309  }
310
311  /**
312   * Writes a sfixed64 value field to the buffer.
313   * @param {number} fieldNumber
314   */
315  writeEndGroup(fieldNumber) {
316    this.writeTag(fieldNumber, WireType.END_GROUP);
317  }
318
319  /**
320   * Writes a uint32 value field to the buffer as a varint without tag.
321   * @param {number} value
322   * @private
323   */
324  writeUint32Value_(value) {
325    this.writeVarint64_(0, value);
326  }
327
328  /**
329   * Writes a uint32 value field to the buffer as a varint.
330   * @param {number} fieldNumber
331   * @param {number} value
332   */
333  writeUint32(fieldNumber, value) {
334    this.writeTag(fieldNumber, WireType.VARINT);
335    this.writeUint32Value_(value);
336  }
337
338  /**
339   * Writes the bits of a 64 bit number to the buffer as a varint.
340   * @param {number} highBits
341   * @param {number} lowBits
342   * @private
343   */
344  writeVarint64_(highBits, lowBits) {
345    for (let i = 0; i < 28; i = i + 7) {
346      const shift = lowBits >>> i;
347      const hasNext = !((shift >>> 7) === 0 && highBits === 0);
348      const byte = (hasNext ? shift | 0x80 : shift) & 0xFF;
349      this.currentBuffer_.push(byte);
350      if (!hasNext) {
351        return;
352      }
353    }
354
355    const splitBits = ((lowBits >>> 28) & 0x0F) | ((highBits & 0x07) << 4);
356    const hasMoreBits = !((highBits >> 3) === 0);
357    this.currentBuffer_.push(
358        (hasMoreBits ? splitBits | 0x80 : splitBits) & 0xFF);
359
360    if (!hasMoreBits) {
361      return;
362    }
363
364    for (let i = 3; i < 31; i = i + 7) {
365      const shift = highBits >>> i;
366      const hasNext = !((shift >>> 7) === 0);
367      const byte = (hasNext ? shift | 0x80 : shift) & 0xFF;
368      this.currentBuffer_.push(byte);
369      if (!hasNext) {
370        return;
371      }
372    }
373
374    this.currentBuffer_.push((highBits >>> 31) & 0x01);
375  }
376
377  /**
378   * Writes a sint32 value field to the buffer as a varint without tag.
379   * @param {number} value
380   * @private
381   */
382  writeSint32Value_(value) {
383    value = (value << 1) ^ (value >> 31);
384    this.writeVarint64_(0, value);
385  }
386
387  /**
388   * Writes a sint32 value field to the buffer as a varint.
389   * @param {number} fieldNumber
390   * @param {number} value
391   */
392  writeSint32(fieldNumber, value) {
393    this.writeTag(fieldNumber, WireType.VARINT);
394    this.writeSint32Value_(value);
395  }
396
397  /**
398   * Writes a sint64 value field to the buffer as a varint without tag.
399   * @param {!Int64} value
400   * @private
401   */
402  writeSint64Value_(value) {
403    const highBits = value.getHighBits();
404    const lowBits = value.getLowBits();
405
406    const sign = highBits >> 31;
407    const encodedLowBits = (lowBits << 1) ^ sign;
408    const encodedHighBits = ((highBits << 1) | (lowBits >>> 31)) ^ sign;
409    this.writeVarint64_(encodedHighBits, encodedLowBits);
410  }
411
412  /**
413   * Writes a sint64 value field to the buffer as a varint.
414   * @param {number} fieldNumber
415   * @param {!Int64} value
416   */
417  writeSint64(fieldNumber, value) {
418    this.writeTag(fieldNumber, WireType.VARINT);
419    this.writeSint64Value_(value);
420  }
421
422  /**
423   * Writes a string value field to the buffer as a varint.
424   * @param {number} fieldNumber
425   * @param {string} value
426   */
427  writeString(fieldNumber, value) {
428    this.writeTag(fieldNumber, WireType.DELIMITED);
429    const array = encoderFunction(value);
430    this.writeUnsignedVarint32_(array.length);
431    this.closeAndStartNewBuffer_();
432    this.blocks_.push(array);
433  }
434
435  /**
436   * Writes raw bytes to the buffer.
437   * @param {!ArrayBuffer} arrayBuffer
438   * @private
439   */
440  writeRaw_(arrayBuffer) {
441    this.closeAndStartNewBuffer_();
442    this.blocks_.push(new Uint8Array(arrayBuffer));
443  }
444
445  /**
446   * Writes raw bytes to the buffer.
447   * @param {!BufferDecoder} bufferDecoder
448   * @param {number} start
449   * @param {!WireType} wireType
450   * @param {number} fieldNumber
451   * @package
452   */
453  writeBufferDecoder(bufferDecoder, start, wireType, fieldNumber) {
454    this.closeAndStartNewBuffer_();
455    const dataLength =
456        getTagLength(bufferDecoder, start, wireType, fieldNumber);
457    this.blocks_.push(
458        bufferDecoder.subBufferDecoder(start, dataLength).asUint8Array());
459  }
460
461  /**
462   * Write the whole bytes as a length delimited field.
463   * @param {number} fieldNumber
464   * @param {!ArrayBuffer} arrayBuffer
465   */
466  writeDelimited(fieldNumber, arrayBuffer) {
467    this.writeTag(fieldNumber, WireType.DELIMITED);
468    this.writeUnsignedVarint32_(arrayBuffer.byteLength);
469    this.writeRaw_(arrayBuffer);
470  }
471
472  /****************************************************************************
473   *                        REPEATED METHODS
474   ****************************************************************************/
475
476  /**
477   * Writes repeated boolean values to the buffer as unpacked varints.
478   * @param {number} fieldNumber
479   * @param {!Array<boolean>} values
480   */
481  writeRepeatedBool(fieldNumber, values) {
482    values.forEach(val => this.writeBool(fieldNumber, val));
483  }
484
485  /**
486   * Writes repeated boolean values to the buffer as packed varints.
487   * @param {number} fieldNumber
488   * @param {!Array<boolean>} values
489   */
490  writePackedBool(fieldNumber, values) {
491    this.writeFixedPacked_(
492        fieldNumber, values, val => this.writeBoolValue_(val), 1);
493  }
494
495  /**
496   * Writes repeated double values to the buffer as unpacked fixed64.
497   * @param {number} fieldNumber
498   * @param {!Array<number>} values
499   */
500  writeRepeatedDouble(fieldNumber, values) {
501    values.forEach(val => this.writeDouble(fieldNumber, val));
502  }
503
504  /**
505   * Writes repeated double values to the buffer as packed fixed64.
506   * @param {number} fieldNumber
507   * @param {!Array<number>} values
508   */
509  writePackedDouble(fieldNumber, values) {
510    this.writeFixedPacked_(
511        fieldNumber, values, val => this.writeDoubleValue_(val), 8);
512  }
513
514  /**
515   * Writes repeated fixed32 values to the buffer as unpacked fixed32.
516   * @param {number} fieldNumber
517   * @param {!Array<number>} values
518   */
519  writeRepeatedFixed32(fieldNumber, values) {
520    values.forEach(val => this.writeFixed32(fieldNumber, val));
521  }
522
523  /**
524   * Writes repeated fixed32 values to the buffer as packed fixed32.
525   * @param {number} fieldNumber
526   * @param {!Array<number>} values
527   */
528  writePackedFixed32(fieldNumber, values) {
529    this.writeFixedPacked_(
530        fieldNumber, values, val => this.writeFixed32Value_(val), 4);
531  }
532
533  /**
534   * Writes repeated float values to the buffer as unpacked fixed64.
535   * @param {number} fieldNumber
536   * @param {!Array<number>} values
537   */
538  writeRepeatedFloat(fieldNumber, values) {
539    values.forEach(val => this.writeFloat(fieldNumber, val));
540  }
541
542  /**
543   * Writes repeated float values to the buffer as packed fixed64.
544   * @param {number} fieldNumber
545   * @param {!Array<number>} values
546   */
547  writePackedFloat(fieldNumber, values) {
548    this.writeFixedPacked_(
549        fieldNumber, values, val => this.writeFloatValue_(val), 4);
550  }
551
552  /**
553   * Writes repeated int32 values to the buffer as unpacked int32.
554   * @param {number} fieldNumber
555   * @param {!Array<number>} values
556   */
557  writeRepeatedInt32(fieldNumber, values) {
558    values.forEach(val => this.writeInt32(fieldNumber, val));
559  }
560
561  /**
562   * Writes repeated int32 values to the buffer as packed int32.
563   * @param {number} fieldNumber
564   * @param {!Array<number>} values
565   */
566  writePackedInt32(fieldNumber, values) {
567    this.writeVariablePacked_(
568        fieldNumber, values, (writer, val) => writer.writeInt32Value_(val));
569  }
570
571  /**
572   * Writes repeated int64 values to the buffer as unpacked varint.
573   * @param {number} fieldNumber
574   * @param {!Array<!Int64>} values
575   */
576  writeRepeatedInt64(fieldNumber, values) {
577    values.forEach(val => this.writeInt64(fieldNumber, val));
578  }
579
580  /**
581   * Writes repeated int64 values to the buffer as packed varint.
582   * @param {number} fieldNumber
583   * @param {!Array<!Int64>} values
584   */
585  writePackedInt64(fieldNumber, values) {
586    this.writeVariablePacked_(
587        fieldNumber, values,
588        (writer, val) =>
589            writer.writeVarint64_(val.getHighBits(), val.getLowBits()));
590  }
591
592  /**
593   * Writes repeated sfixed32 values to the buffer as unpacked fixed32.
594   * @param {number} fieldNumber
595   * @param {!Array<number>} values
596   */
597  writeRepeatedSfixed32(fieldNumber, values) {
598    values.forEach(val => this.writeSfixed32(fieldNumber, val));
599  }
600
601  /**
602   * Writes repeated sfixed32 values to the buffer as packed fixed32.
603   * @param {number} fieldNumber
604   * @param {!Array<number>} values
605   */
606  writePackedSfixed32(fieldNumber, values) {
607    this.writeFixedPacked_(
608        fieldNumber, values, val => this.writeSfixed32Value_(val), 4);
609  }
610
611  /**
612   * Writes repeated sfixed64 values to the buffer as unpacked fixed64.
613   * @param {number} fieldNumber
614   * @param {!Array<!Int64>} values
615   */
616  writeRepeatedSfixed64(fieldNumber, values) {
617    values.forEach(val => this.writeSfixed64(fieldNumber, val));
618  }
619
620  /**
621   * Writes repeated sfixed64 values to the buffer as packed fixed64.
622   * @param {number} fieldNumber
623   * @param {!Array<!Int64>} values
624   */
625  writePackedSfixed64(fieldNumber, values) {
626    this.writeFixedPacked_(
627        fieldNumber, values, val => this.writeSfixed64Value_(val), 8);
628  }
629
630  /**
631   * Writes repeated sint32 values to the buffer as unpacked sint32.
632   * @param {number} fieldNumber
633   * @param {!Array<number>} values
634   */
635  writeRepeatedSint32(fieldNumber, values) {
636    values.forEach(val => this.writeSint32(fieldNumber, val));
637  }
638
639  /**
640   * Writes repeated sint32 values to the buffer as packed sint32.
641   * @param {number} fieldNumber
642   * @param {!Array<number>} values
643   */
644  writePackedSint32(fieldNumber, values) {
645    this.writeVariablePacked_(
646        fieldNumber, values, (writer, val) => writer.writeSint32Value_(val));
647  }
648
649  /**
650   * Writes repeated sint64 values to the buffer as unpacked varint.
651   * @param {number} fieldNumber
652   * @param {!Array<!Int64>} values
653   */
654  writeRepeatedSint64(fieldNumber, values) {
655    values.forEach(val => this.writeSint64(fieldNumber, val));
656  }
657
658  /**
659   * Writes repeated sint64 values to the buffer as packed varint.
660   * @param {number} fieldNumber
661   * @param {!Array<!Int64>} values
662   */
663  writePackedSint64(fieldNumber, values) {
664    this.writeVariablePacked_(
665        fieldNumber, values, (writer, val) => writer.writeSint64Value_(val));
666  }
667
668  /**
669   * Writes repeated uint32 values to the buffer as unpacked uint32.
670   * @param {number} fieldNumber
671   * @param {!Array<number>} values
672   */
673  writeRepeatedUint32(fieldNumber, values) {
674    values.forEach(val => this.writeUint32(fieldNumber, val));
675  }
676
677  /**
678   * Writes repeated uint32 values to the buffer as packed uint32.
679   * @param {number} fieldNumber
680   * @param {!Array<number>} values
681   */
682  writePackedUint32(fieldNumber, values) {
683    this.writeVariablePacked_(
684        fieldNumber, values, (writer, val) => writer.writeUint32Value_(val));
685  }
686
687  /**
688   * Writes repeated bytes values to the buffer.
689   * @param {number} fieldNumber
690   * @param {!Array<!ByteString>} values
691   */
692  writeRepeatedBytes(fieldNumber, values) {
693    values.forEach(val => this.writeBytes(fieldNumber, val));
694  }
695
696  /**
697   * Writes packed fields with fixed length.
698   * @param {number} fieldNumber
699   * @param {!Array<T>} values
700   * @param {function(T)} valueWriter
701   * @param {number} entitySize
702   * @template T
703   * @private
704   */
705  writeFixedPacked_(fieldNumber, values, valueWriter, entitySize) {
706    if (values.length === 0) {
707      return;
708    }
709    this.writeTag(fieldNumber, WireType.DELIMITED);
710    this.writeUnsignedVarint32_(values.length * entitySize);
711    this.closeAndStartNewBuffer_();
712    values.forEach(value => valueWriter(value));
713  }
714
715  /**
716   * Writes packed fields with variable length.
717   * @param {number} fieldNumber
718   * @param {!Array<T>} values
719   * @param {function(!Writer, T)} valueWriter
720   * @template T
721   * @private
722   */
723  writeVariablePacked_(fieldNumber, values, valueWriter) {
724    if (values.length === 0) {
725      return;
726    }
727    const writer = new Writer();
728    values.forEach(val => valueWriter(writer, val));
729    const bytes = writer.getAndResetResultBuffer();
730    this.writeDelimited(fieldNumber, bytes);
731  }
732
733  /**
734   * Writes repeated string values to the buffer.
735   * @param {number} fieldNumber
736   * @param {!Array<string>} values
737   */
738  writeRepeatedString(fieldNumber, values) {
739    values.forEach(val => this.writeString(fieldNumber, val));
740  }
741}
742
743exports = Writer;
744