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