Add dependencies locally

This commit is contained in:
Ahrimdon
2024-02-27 03:09:30 -05:00
parent 1679ef60cc
commit 70e8a8502b
5698 changed files with 2770161 additions and 12 deletions

View File

@ -0,0 +1,130 @@
goog.module('protobuf.runtime.BinaryStorage');
const Storage = goog.require('protobuf.runtime.Storage');
const {checkDefAndNotNull} = goog.require('protobuf.internal.checks');
/**
* Class storing all the fields of a binary protobuf message.
*
* @package
* @template FieldType
* @implements {Storage}
*/
class BinaryStorage {
/**
* @param {number=} pivot
*/
constructor(pivot = Storage.DEFAULT_PIVOT) {
/**
* Fields having a field number no greater than the pivot value are stored
* into an array for fast access. A field with field number X is stored into
* the array position X - 1.
*
* @private @const {!Array<!FieldType|undefined>}
*/
this.array_ = new Array(pivot);
/**
* Fields having a field number higher than the pivot value are stored into
* the map. We create the map only when it's needed, since even an empty map
* takes up a significant amount of memory.
*
* @private {?Map<number, !FieldType>}
*/
this.map_ = null;
}
/**
* Fields having a field number no greater than the pivot value are stored
* into an array for fast access. A field with field number X is stored into
* the array position X - 1.
* @return {number}
* @override
*/
getPivot() {
return this.array_.length;
}
/**
* Sets a field in the specified field number.
*
* @param {number} fieldNumber
* @param {!FieldType} field
* @override
*/
set(fieldNumber, field) {
if (fieldNumber <= this.getPivot()) {
this.array_[fieldNumber - 1] = field;
} else {
if (this.map_) {
this.map_.set(fieldNumber, field);
} else {
this.map_ = new Map([[fieldNumber, field]]);
}
}
}
/**
* Returns a field at the specified field number.
*
* @param {number} fieldNumber
* @return {!FieldType|undefined}
* @override
*/
get(fieldNumber) {
if (fieldNumber <= this.getPivot()) {
return this.array_[fieldNumber - 1];
} else {
return this.map_ ? this.map_.get(fieldNumber) : undefined;
}
}
/**
* Deletes a field from the specified field number.
*
* @param {number} fieldNumber
* @override
*/
delete(fieldNumber) {
if (fieldNumber <= this.getPivot()) {
delete this.array_[fieldNumber - 1];
} else {
if (this.map_) {
this.map_.delete(fieldNumber);
}
}
}
/**
* Executes the provided function once for each field.
*
* @param {function(!FieldType, number): void} callback
* @override
*/
forEach(callback) {
this.array_.forEach((field, fieldNumber) => {
if (field) {
callback(checkDefAndNotNull(field), fieldNumber + 1);
}
});
if (this.map_) {
this.map_.forEach(callback);
}
}
/**
* Creates a shallow copy of the storage.
*
* @return {!BinaryStorage}
* @override
*/
shallowCopy() {
const copy = new BinaryStorage(this.getPivot());
this.forEach(
(field, fieldNumber) =>
void copy.set(fieldNumber, field.shallowCopy()));
return copy;
}
}
exports = BinaryStorage;

View File

@ -0,0 +1,165 @@
/**
* @fileoverview Tests for storage.js.
*/
goog.module('protobuf.runtime.BinaryStorageTest');
goog.setTestOnly();
const BinaryStorage = goog.require('protobuf.runtime.BinaryStorage');
const {Field} = goog.require('protobuf.binary.field');
/**
* @type {number}
*/
const DEFAULT_PIVOT = 24;
const /** !Field */ field1 =
Field.fromDecodedValue(/* decodedValue= */ 1, /* encoder= */ () => {});
const /** !Field */ field2 =
Field.fromDecodedValue(/* decodedValue= */ 2, /* encoder= */ () => {});
const /** !Field */ field3 =
Field.fromDecodedValue(/* decodedValue= */ 3, /* encoder= */ () => {});
const /** !Field */ field4 =
Field.fromDecodedValue(/* decodedValue= */ 4, /* encoder= */ () => {});
/**
* Returns the number of fields stored.
*
* @param {!BinaryStorage} storage
* @return {number}
*/
function getStorageSize(storage) {
let size = 0;
storage.forEach(() => void size++);
return size;
}
describe('BinaryStorage', () => {
it('sets and gets a field not greater than the pivot', () => {
const storage = new BinaryStorage(DEFAULT_PIVOT);
storage.set(1, field1);
storage.set(DEFAULT_PIVOT, field2);
expect(storage.getPivot()).toBe(DEFAULT_PIVOT);
expect(storage.get(1)).toBe(field1);
expect(storage.get(DEFAULT_PIVOT)).toBe(field2);
});
it('sets and gets a field greater than the pivot', () => {
const storage = new BinaryStorage(DEFAULT_PIVOT);
storage.set(DEFAULT_PIVOT + 1, field1);
storage.set(100000, field2);
expect(storage.get(DEFAULT_PIVOT + 1)).toBe(field1);
expect(storage.get(100000)).toBe(field2);
});
it('sets and gets a field when pivot is zero', () => {
const storage = new BinaryStorage(0);
storage.set(0, field1);
storage.set(100000, field2);
expect(storage.getPivot()).toBe(0);
expect(storage.get(0)).toBe(field1);
expect(storage.get(100000)).toBe(field2);
});
it('sets and gets a field when pivot is undefined', () => {
const storage = new BinaryStorage();
storage.set(0, field1);
storage.set(DEFAULT_PIVOT, field2);
storage.set(DEFAULT_PIVOT + 1, field3);
expect(storage.getPivot()).toBe(DEFAULT_PIVOT);
expect(storage.get(0)).toBe(field1);
expect(storage.get(DEFAULT_PIVOT)).toBe(field2);
expect(storage.get(DEFAULT_PIVOT + 1)).toBe(field3);
});
it('returns undefined for nonexistent fields', () => {
const storage = new BinaryStorage(DEFAULT_PIVOT);
expect(storage.get(1)).toBeUndefined();
expect(storage.get(DEFAULT_PIVOT)).toBeUndefined();
expect(storage.get(DEFAULT_PIVOT + 1)).toBeUndefined();
expect(storage.get(100000)).toBeUndefined();
});
it('returns undefined for nonexistent fields after map initialization',
() => {
const storage = new BinaryStorage(DEFAULT_PIVOT);
storage.set(100001, field1);
expect(storage.get(1)).toBeUndefined();
expect(storage.get(DEFAULT_PIVOT)).toBeUndefined();
expect(storage.get(DEFAULT_PIVOT + 1)).toBeUndefined();
expect(storage.get(100000)).toBeUndefined();
});
it('deletes a field in delete() when values are only in array', () => {
const storage = new BinaryStorage(DEFAULT_PIVOT);
storage.set(1, field1);
storage.delete(1);
expect(storage.get(1)).toBeUndefined();
});
it('deletes a field in delete() when values are both in array and map',
() => {
const storage = new BinaryStorage(DEFAULT_PIVOT);
storage.set(DEFAULT_PIVOT, field2);
storage.set(DEFAULT_PIVOT + 1, field3);
storage.delete(DEFAULT_PIVOT);
storage.delete(DEFAULT_PIVOT + 1);
expect(storage.get(DEFAULT_PIVOT)).toBeUndefined();
expect(storage.get(DEFAULT_PIVOT + 1)).toBeUndefined();
});
it('deletes a field in delete() when values are only in map', () => {
const storage = new BinaryStorage(DEFAULT_PIVOT);
storage.set(100000, field4);
storage.delete(100000);
expect(storage.get(100000)).toBeUndefined();
});
it('loops over all the elements in forEach()', () => {
const storage = new BinaryStorage(DEFAULT_PIVOT);
storage.set(1, field1);
storage.set(DEFAULT_PIVOT, field2);
storage.set(DEFAULT_PIVOT + 1, field3);
storage.set(100000, field4);
const fields = new Map();
storage.forEach(
(field, fieldNumber) => void fields.set(fieldNumber, field));
expect(fields.size).toEqual(4);
expect(fields.get(1)).toBe(field1);
expect(storage.get(DEFAULT_PIVOT)).toBe(field2);
expect(storage.get(DEFAULT_PIVOT + 1)).toBe(field3);
expect(fields.get(100000)).toBe(field4);
});
it('creates a shallow copy of the storage in shallowCopy()', () => {
const storage = new BinaryStorage(DEFAULT_PIVOT);
storage.set(1, field1);
storage.set(100000, field2);
const copy = storage.shallowCopy();
expect(getStorageSize(copy)).toEqual(2);
expect(copy.get(1)).not.toBe(field1);
expect(copy.get(1).getDecodedValue()).toEqual(1);
expect(copy.get(100000)).not.toBe(field1);
expect(copy.get(100000).getDecodedValue()).toEqual(2);
});
});

View File

@ -0,0 +1,79 @@
/**
* @fileoverview Test data for bool encoding and decoding.
*/
goog.module('protobuf.binary.boolTestPairs');
const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
/**
* An array of Pairs of boolean values and their bit representation.
* This is used to test encoding and decoding from/to the protobuf wire format.
* @return {!Array<{name: string, boolValue: boolean, bufferDecoder:
* !BufferDecoder, error: ?boolean, skip_writer: ?boolean}>}
*/
function getBoolPairs() {
const boolPairs = [
{
name: 'true',
boolValue: true,
bufferDecoder: createBufferDecoder(0x01),
},
{
name: 'false',
boolValue: false,
bufferDecoder: createBufferDecoder(0x00),
},
{
name: 'two-byte true',
boolValue: true,
bufferDecoder: createBufferDecoder(0x80, 0x01),
skip_writer: true,
},
{
name: 'two-byte false',
boolValue: false,
bufferDecoder: createBufferDecoder(0x80, 0x00),
skip_writer: true,
},
{
name: 'minus one',
boolValue: true,
bufferDecoder: createBufferDecoder(
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01),
skip_writer: true,
},
{
name: 'max signed int 2^63 - 1',
boolValue: true,
bufferDecoder: createBufferDecoder(
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F),
skip_writer: true,
},
{
name: 'min signed int -2^63',
boolValue: true,
bufferDecoder: createBufferDecoder(
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01),
skip_writer: true,
},
{
name: 'overflowed but valid varint',
boolValue: false,
bufferDecoder: createBufferDecoder(
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x02),
skip_writer: true,
},
{
name: 'errors out for 11 bytes',
boolValue: true,
bufferDecoder: createBufferDecoder(
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF),
error: true,
skip_writer: true,
},
];
return [...boolPairs];
}
exports = {getBoolPairs};

View File

@ -0,0 +1,343 @@
/**
* @fileoverview A buffer implementation that can decode data for protobufs.
*/
goog.module('protobuf.binary.BufferDecoder');
const ByteString = goog.require('protobuf.ByteString');
const functions = goog.require('goog.functions');
const {POLYFILL_TEXT_ENCODING, checkCriticalPositionIndex, checkCriticalState, checkState} = goog.require('protobuf.internal.checks');
const {byteStringFromUint8ArrayUnsafe} = goog.require('protobuf.byteStringInternal');
const {concatenateByteArrays} = goog.require('protobuf.binary.uint8arrays');
const {decode} = goog.require('protobuf.binary.textencoding');
/**
* Returns a valid utf-8 decoder function based on TextDecoder if available or
* a polyfill.
* Some of the environments we run in do not have TextDecoder defined.
* TextDecoder is faster than our polyfill so we prefer it over the polyfill.
* @return {function(!DataView): string}
*/
function getStringDecoderFunction() {
if (goog.global['TextDecoder']) {
const textDecoder = new goog.global['TextDecoder']('utf-8', {fatal: true});
return bytes => textDecoder.decode(bytes);
}
if (POLYFILL_TEXT_ENCODING) {
return decode;
} else {
throw new Error(
'TextDecoder is missing. ' +
'Enable protobuf.defines.POLYFILL_TEXT_ENCODING.');
}
}
/** @type {function(): function(!DataView): string} */
const stringDecoderFunction =
functions.cacheReturnValue(() => getStringDecoderFunction());
/** @type {function(): !DataView} */
const emptyDataView =
functions.cacheReturnValue(() => new DataView(new ArrayBuffer(0)));
class BufferDecoder {
/**
* @param {!Array<!BufferDecoder>} bufferDecoders
* @return {!BufferDecoder}
*/
static merge(bufferDecoders) {
const uint8Arrays = bufferDecoders.map(b => b.asUint8Array());
const bytesArray = concatenateByteArrays(uint8Arrays);
return BufferDecoder.fromArrayBuffer(bytesArray.buffer);
}
/**
* @param {!ArrayBuffer} arrayBuffer
* @return {!BufferDecoder}
*/
static fromArrayBuffer(arrayBuffer) {
return new BufferDecoder(
new DataView(arrayBuffer), 0, arrayBuffer.byteLength);
}
/**
* @param {!DataView} dataView
* @param {number} startIndex
* @param {number} length
* @private
*/
constructor(dataView, startIndex, length) {
/** @private @const {!DataView} */
this.dataView_ = dataView;
/** @private @const {number} */
this.startIndex_ = startIndex;
/** @private @const {number} */
this.endIndex_ = startIndex + length;
/** @private {number} */
this.cursor_ = startIndex;
}
/**
* Returns the start index of the underlying buffer.
* @return {number}
*/
startIndex() {
return this.startIndex_;
}
/**
* Returns the end index of the underlying buffer.
* @return {number}
*/
endIndex() {
return this.endIndex_;
}
/**
* Returns the length of the underlying buffer.
* @return {number}
*/
length() {
return this.endIndex_ - this.startIndex_;
}
/**
* Returns the start position of the next data, i.e. end position of the last
* read data + 1.
* @return {number}
*/
cursor() {
return this.cursor_;
}
/**
* Sets the cursor to the specified position.
* @param {number} position
*/
setCursor(position) {
this.cursor_ = position;
}
/**
* Returns if there is more data to read after the current cursor position.
* @return {boolean}
*/
hasNext() {
return this.cursor_ < this.endIndex_;
}
/**
* Returns a float32 from a given index
* @param {number} index
* @return {number}
*/
getFloat32(index) {
this.cursor_ = index + 4;
return this.dataView_.getFloat32(index, true);
}
/**
* Returns a float64 from a given index
* @param {number} index
* @return {number}
*/
getFloat64(index) {
this.cursor_ = index + 8;
return this.dataView_.getFloat64(index, true);
}
/**
* Returns an int32 from a given index
* @param {number} index
* @return {number}
*/
getInt32(index) {
this.cursor_ = index + 4;
return this.dataView_.getInt32(index, true);
}
/**
* Returns a uint32 from a given index
* @param {number} index
* @return {number}
*/
getUint32(index) {
this.cursor_ = index + 4;
return this.dataView_.getUint32(index, true);
}
/**
* Returns two JS numbers each representing 32 bits of a 64 bit number. Also
* sets the cursor to the start of the next block of data.
* @param {number} index
* @return {{lowBits: number, highBits: number}}
*/
getVarint(index) {
this.cursor_ = index;
let lowBits = 0;
let highBits = 0;
for (let shift = 0; shift < 28; shift += 7) {
const b = this.dataView_.getUint8(this.cursor_++);
lowBits |= (b & 0x7F) << shift;
if ((b & 0x80) === 0) {
return {lowBits, highBits};
}
}
const middleByte = this.dataView_.getUint8(this.cursor_++);
// last four bits of the first 32 bit number
lowBits |= (middleByte & 0x0F) << 28;
// 3 upper bits are part of the next 32 bit number
highBits = (middleByte & 0x70) >> 4;
if ((middleByte & 0x80) === 0) {
return {lowBits, highBits};
}
for (let shift = 3; shift <= 31; shift += 7) {
const b = this.dataView_.getUint8(this.cursor_++);
highBits |= (b & 0x7F) << shift;
if ((b & 0x80) === 0) {
return {lowBits, highBits};
}
}
checkCriticalState(false, 'Data is longer than 10 bytes');
return {lowBits, highBits};
}
/**
* Returns an unsigned int32 number at the current cursor position. The upper
* bits are discarded if the varint is longer than 32 bits. Also sets the
* cursor to the start of the next block of data.
* @return {number}
*/
getUnsignedVarint32() {
let b = this.dataView_.getUint8(this.cursor_++);
let result = b & 0x7F;
if ((b & 0x80) === 0) {
return result;
}
b = this.dataView_.getUint8(this.cursor_++);
result |= (b & 0x7F) << 7;
if ((b & 0x80) === 0) {
return result;
}
b = this.dataView_.getUint8(this.cursor_++);
result |= (b & 0x7F) << 14;
if ((b & 0x80) === 0) {
return result;
}
b = this.dataView_.getUint8(this.cursor_++);
result |= (b & 0x7F) << 21;
if ((b & 0x80) === 0) {
return result;
}
// Extract only last 4 bits
b = this.dataView_.getUint8(this.cursor_++);
result |= (b & 0x0F) << 28;
for (let readBytes = 5; ((b & 0x80) !== 0) && readBytes < 10; readBytes++) {
b = this.dataView_.getUint8(this.cursor_++);
}
checkCriticalState((b & 0x80) === 0, 'Data is longer than 10 bytes');
// Result can be have 32 bits, convert it to unsigned
return result >>> 0;
}
/**
* Returns an unsigned int32 number at the specified index. The upper bits are
* discarded if the varint is longer than 32 bits. Also sets the cursor to the
* start of the next block of data.
* @param {number} index
* @return {number}
*/
getUnsignedVarint32At(index) {
this.cursor_ = index;
return this.getUnsignedVarint32();
}
/**
* Seeks forward by the given amount.
* @param {number} skipAmount
* @package
*/
skip(skipAmount) {
this.cursor_ += skipAmount;
checkCriticalPositionIndex(this.cursor_, this.endIndex_);
}
/**
* Skips over a varint from the current cursor position.
* @package
*/
skipVarint() {
const startIndex = this.cursor_;
while (this.dataView_.getUint8(this.cursor_++) & 0x80) {
}
checkCriticalPositionIndex(this.cursor_, startIndex + 10);
}
/**
* @param {number} startIndex
* @param {number} length
* @return {!BufferDecoder}
*/
subBufferDecoder(startIndex, length) {
checkState(
startIndex >= this.startIndex(),
`Current start: ${this.startIndex()}, subBufferDecoder start: ${
startIndex}`);
checkState(length >= 0, `Length: ${length}`);
checkState(
startIndex + length <= this.endIndex(),
`Current end: ${this.endIndex()}, subBufferDecoder start: ${
startIndex}, subBufferDecoder length: ${length}`);
return new BufferDecoder(this.dataView_, startIndex, length);
}
/**
* Returns the buffer as a string.
* @return {string}
*/
asString() {
// TODO: Remove this check when we no longer need to support IE
const stringDataView = this.length() === 0 ?
emptyDataView() :
new DataView(this.dataView_.buffer, this.startIndex_, this.length());
return stringDecoderFunction()(stringDataView);
}
/**
* Returns the buffer as a ByteString.
* @return {!ByteString}
*/
asByteString() {
return byteStringFromUint8ArrayUnsafe(this.asUint8Array());
}
/**
* Returns the DataView as an Uint8Array. DO NOT MODIFY or expose the
* underlying buffer.
*
* @package
* @return {!Uint8Array}
*/
asUint8Array() {
return new Uint8Array(
this.dataView_.buffer, this.startIndex_, this.length());
}
}
exports = BufferDecoder;

View File

@ -0,0 +1,18 @@
/**
* @fileoverview Helper methods to create BufferDecoders.
*/
goog.module('protobuf.binary.bufferDecoderHelper');
const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
/**
* @param {...number} bytes
* @return {!BufferDecoder}
*/
function createBufferDecoder(...bytes) {
return BufferDecoder.fromArrayBuffer(new Uint8Array(bytes).buffer);
}
exports = {
createBufferDecoder,
};

View File

@ -0,0 +1,242 @@
/**
* @fileoverview Tests for BufferDecoder.
*/
goog.module('protobuf.binary.varintsTest');
const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
const {CHECK_CRITICAL_STATE, CHECK_STATE} = goog.require('protobuf.internal.checks');
goog.setTestOnly();
/**
* @param {...number} bytes
* @return {!ArrayBuffer}
*/
function createArrayBuffer(...bytes) {
return new Uint8Array(bytes).buffer;
}
describe('setCursor does', () => {
it('set the cursor at the position specified', () => {
const bufferDecoder =
BufferDecoder.fromArrayBuffer(createArrayBuffer(0x0, 0x1));
expect(bufferDecoder.cursor()).toBe(0);
bufferDecoder.setCursor(1);
expect(bufferDecoder.cursor()).toBe(1);
});
});
describe('skip does', () => {
it('advance the cursor', () => {
const bufferDecoder =
BufferDecoder.fromArrayBuffer(createArrayBuffer(0x0, 0x1, 0x2));
bufferDecoder.setCursor(1);
bufferDecoder.skip(1);
expect(bufferDecoder.cursor()).toBe(2);
});
});
describe('Skip varint does', () => {
it('skip a varint', () => {
const bufferDecoder =
BufferDecoder.fromArrayBuffer(createArrayBuffer(0x01));
bufferDecoder.skipVarint();
expect(bufferDecoder.cursor()).toBe(1);
});
it('fail when varint is larger than 10 bytes', () => {
const bufferDecoder = BufferDecoder.fromArrayBuffer(createArrayBuffer(
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00));
if (CHECK_CRITICAL_STATE) {
expect(() => bufferDecoder.skipVarint()).toThrow();
} else {
// Note in unchecked mode we produce invalid output for invalid inputs.
// This test just documents our behavior in those cases.
// These values might change at any point and are not considered
// what the implementation should be doing here.
bufferDecoder.skipVarint();
expect(bufferDecoder.cursor()).toBe(11);
}
});
it('fail when varint is beyond end of underlying array', () => {
const bufferDecoder =
BufferDecoder.fromArrayBuffer(createArrayBuffer(0x80, 0x80));
expect(() => bufferDecoder.skipVarint()).toThrow();
});
});
describe('readVarint64 does', () => {
it('read zero', () => {
const bufferDecoder =
BufferDecoder.fromArrayBuffer(createArrayBuffer(0x00));
const {lowBits, highBits} = bufferDecoder.getVarint(0);
expect(lowBits).toBe(0);
expect(highBits).toBe(0);
expect(bufferDecoder.cursor()).toBe(1);
});
it('read one', () => {
const bufferDecoder =
BufferDecoder.fromArrayBuffer(createArrayBuffer(0x01));
const {lowBits, highBits} = bufferDecoder.getVarint(0);
expect(lowBits).toBe(1);
expect(highBits).toBe(0);
expect(bufferDecoder.cursor()).toBe(1);
});
it('read max value', () => {
const bufferDecoder = BufferDecoder.fromArrayBuffer(createArrayBuffer(
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01));
const {lowBits, highBits} = bufferDecoder.getVarint(0);
expect(lowBits).toBe(-1);
expect(highBits).toBe(-1);
expect(bufferDecoder.cursor()).toBe(10);
});
});
describe('readUnsignedVarint32 does', () => {
it('read zero', () => {
const bufferDecoder =
BufferDecoder.fromArrayBuffer(createArrayBuffer(0x00));
const result = bufferDecoder.getUnsignedVarint32();
expect(result).toBe(0);
expect(bufferDecoder.cursor()).toBe(1);
});
it('read one', () => {
const bufferDecoder =
BufferDecoder.fromArrayBuffer(createArrayBuffer(0x01));
const result = bufferDecoder.getUnsignedVarint32();
expect(result).toBe(1);
expect(bufferDecoder.cursor()).toBe(1);
});
it('read max int32', () => {
const bufferDecoder = BufferDecoder.fromArrayBuffer(
createArrayBuffer(0xFF, 0xFF, 0xFF, 0xFF, 0x0F));
const result = bufferDecoder.getUnsignedVarint32();
expect(result).toBe(4294967295);
expect(bufferDecoder.cursor()).toBe(5);
});
it('read max value', () => {
const bufferDecoder = BufferDecoder.fromArrayBuffer(createArrayBuffer(
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01));
const result = bufferDecoder.getUnsignedVarint32();
expect(result).toBe(4294967295);
expect(bufferDecoder.cursor()).toBe(10);
});
it('fail if data is longer than 10 bytes', () => {
const bufferDecoder = BufferDecoder.fromArrayBuffer(createArrayBuffer(
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01));
if (CHECK_CRITICAL_STATE) {
expect(() => bufferDecoder.getUnsignedVarint32()).toThrow();
} else {
// Note in unchecked mode we produce invalid output for invalid inputs.
// This test just documents our behavior in those cases.
// These values might change at any point and are not considered
// what the implementation should be doing here.
const result = bufferDecoder.getUnsignedVarint32();
expect(result).toBe(4294967295);
expect(bufferDecoder.cursor()).toBe(10);
}
});
});
describe('readUnsignedVarint32At does', () => {
it('reads from a specific index', () => {
const bufferDecoder =
BufferDecoder.fromArrayBuffer(createArrayBuffer(0x1, 0x2));
const result = bufferDecoder.getUnsignedVarint32At(1);
expect(result).toBe(2);
expect(bufferDecoder.cursor()).toBe(2);
});
});
describe('getFloat32 does', () => {
it('read one', () => {
const bufferDecoder = BufferDecoder.fromArrayBuffer(
createArrayBuffer(0x00, 0x00, 0x80, 0x3F));
const result = bufferDecoder.getFloat32(0);
expect(result).toBe(1);
expect(bufferDecoder.cursor()).toBe(4);
});
});
describe('getFloat64 does', () => {
it('read one', () => {
const bufferDecoder = BufferDecoder.fromArrayBuffer(
createArrayBuffer(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F));
const result = bufferDecoder.getFloat64(0);
expect(result).toBe(1);
expect(bufferDecoder.cursor()).toBe(8);
});
});
describe('getInt32 does', () => {
it('read one', () => {
const bufferDecoder = BufferDecoder.fromArrayBuffer(
createArrayBuffer(0x01, 0x00, 0x00, 0x00));
const result = bufferDecoder.getInt32(0);
expect(result).toBe(1);
expect(bufferDecoder.cursor()).toBe(4);
});
it('read minus one', () => {
const bufferDecoder = BufferDecoder.fromArrayBuffer(
createArrayBuffer(0xFF, 0xFF, 0xFF, 0xFF));
const result = bufferDecoder.getInt32(0);
expect(result).toBe(-1);
expect(bufferDecoder.cursor()).toBe(4);
});
});
describe('getUint32 does', () => {
it('read one', () => {
const bufferDecoder =
BufferDecoder.fromArrayBuffer(createArrayBuffer(0x01, 0x00, 0x00, 0x0));
const result = bufferDecoder.getUint32(0);
expect(result).toBe(1);
expect(bufferDecoder.cursor()).toBe(4);
});
it('read max uint32', () => {
const bufferDecoder = BufferDecoder.fromArrayBuffer(
createArrayBuffer(0xFF, 0xFF, 0xFF, 0xFF));
const result = bufferDecoder.getUint32(0);
expect(result).toBe(4294967295);
expect(bufferDecoder.cursor()).toBe(4);
});
});
describe('subBufferDecoder does', () => {
it('can create valid sub buffers', () => {
const bufferDecoder =
BufferDecoder.fromArrayBuffer(createArrayBuffer(0x00, 0x01, 0x02));
expect(bufferDecoder.subBufferDecoder(0, 0))
.toEqual(BufferDecoder.fromArrayBuffer(createArrayBuffer()));
expect(bufferDecoder.subBufferDecoder(0, 1))
.toEqual(BufferDecoder.fromArrayBuffer(createArrayBuffer(0x00)));
expect(bufferDecoder.subBufferDecoder(1, 0))
.toEqual(BufferDecoder.fromArrayBuffer(createArrayBuffer()));
expect(bufferDecoder.subBufferDecoder(1, 1))
.toEqual(BufferDecoder.fromArrayBuffer(createArrayBuffer(0x01)));
expect(bufferDecoder.subBufferDecoder(1, 2))
.toEqual(BufferDecoder.fromArrayBuffer(createArrayBuffer(0x01, 0x02)));
});
it('can not create invalid', () => {
const bufferDecoder =
BufferDecoder.fromArrayBuffer(createArrayBuffer(0x00, 0x01, 0x02));
if (CHECK_STATE) {
expect(() => bufferDecoder.subBufferDecoder(-1, 1)).toThrow();
expect(() => bufferDecoder.subBufferDecoder(0, -4)).toThrow();
expect(() => bufferDecoder.subBufferDecoder(0, 4)).toThrow();
}
});
});

View File

@ -0,0 +1,91 @@
/**
* @fileoverview Handwritten code of ConformanceRequest.
*/
goog.module('proto.conformance.ConformanceRequest');
const Kernel = goog.require('protobuf.runtime.Kernel');
const WireFormat = goog.require('proto.conformance.WireFormat');
/**
* Handwritten code of conformance.ConformanceRequest.
* This is used to send request from the conformance test runner to the testee.
* Check //third_party/protobuf/testing/protobuf/conformance/conformance.proto
* for more details.
* @final
*/
class ConformanceRequest {
/**
* @param {!ArrayBuffer} bytes
* @private
*/
constructor(bytes) {
/** @private @const {!Kernel} */
this.accessor_ = Kernel.fromArrayBuffer(bytes);
}
/**
* Create a request instance with the given bytes data.
* @param {!ArrayBuffer} bytes
* @return {!ConformanceRequest}
*/
static deserialize(bytes) {
return new ConformanceRequest(bytes);
}
/**
* Gets the protobuf_payload.
* @return {!ArrayBuffer}
*/
getProtobufPayload() {
return this.accessor_.getBytesWithDefault(1).toArrayBuffer();
}
/**
* Gets the requested_output_format.
* @return {!WireFormat}
*/
getRequestedOutputFormat() {
return /** @type {!WireFormat} */ (this.accessor_.getInt32WithDefault(3));
}
/**
* Gets the message_type.
* @return {string}
*/
getMessageType() {
return this.accessor_.getStringWithDefault(4);
}
/**
* Gets the oneof case for payload field.
* This implementation assumes only one field in a oneof group is set.
* @return {!ConformanceRequest.PayloadCase}
*/
getPayloadCase() {
if (this.accessor_.hasFieldNumber(1)) {
return /** @type {!ConformanceRequest.PayloadCase} */ (
ConformanceRequest.PayloadCase.PROTOBUF_PAYLOAD);
} else if (this.accessor_.hasFieldNumber(2)) {
return /** @type {!ConformanceRequest.PayloadCase} */ (
ConformanceRequest.PayloadCase.JSON_PAYLOAD);
} else if (this.accessor_.hasFieldNumber(8)) {
return /** @type {!ConformanceRequest.PayloadCase} */ (
ConformanceRequest.PayloadCase.TEXT_PAYLOAD);
} else {
return /** @type {!ConformanceRequest.PayloadCase} */ (
ConformanceRequest.PayloadCase.PAYLOAD_NOT_SET);
}
}
}
/**
* @enum {number}
*/
ConformanceRequest.PayloadCase = {
PAYLOAD_NOT_SET: 0,
PROTOBUF_PAYLOAD: 1,
JSON_PAYLOAD: 2,
TEXT_PAYLOAD: 8,
};
exports = ConformanceRequest;

View File

@ -0,0 +1,76 @@
/**
* @fileoverview Handwritten code of ConformanceResponse.
*/
goog.module('proto.conformance.ConformanceResponse');
const ByteString = goog.require('protobuf.ByteString');
const Kernel = goog.require('protobuf.runtime.Kernel');
/**
* Handwritten code of conformance.ConformanceResponse.
* This is used to send response from the conformance testee to the test runner.
* Check //third_party/protobuf/testing/protobuf/conformance/conformance.proto
* for more details.
* @final
*/
class ConformanceResponse {
/**
* @param {!ArrayBuffer} bytes
* @private
*/
constructor(bytes) {
/** @private @const {!Kernel} */
this.accessor_ = Kernel.fromArrayBuffer(bytes);
}
/**
* Create an empty response instance.
* @return {!ConformanceResponse}
*/
static createEmpty() {
return new ConformanceResponse(new ArrayBuffer(0));
}
/**
* Sets parse_error field.
* @param {string} value
*/
setParseError(value) {
this.accessor_.setString(1, value);
}
/**
* Sets runtime_error field.
* @param {string} value
*/
setRuntimeError(value) {
this.accessor_.setString(2, value);
}
/**
* Sets protobuf_payload field.
* @param {!ArrayBuffer} value
*/
setProtobufPayload(value) {
const bytesString = ByteString.fromArrayBuffer(value);
this.accessor_.setBytes(3, bytesString);
}
/**
* Sets skipped field.
* @param {string} value
*/
setSkipped(value) {
this.accessor_.setString(5, value);
}
/**
* Serializes into binary data.
* @return {!ArrayBuffer}
*/
serialize() {
return this.accessor_.serialize();
}
}
exports = ConformanceResponse;

View File

@ -0,0 +1,103 @@
goog.module('javascript.protobuf.conformance');
const ConformanceRequest = goog.require('proto.conformance.ConformanceRequest');
const ConformanceResponse = goog.require('proto.conformance.ConformanceResponse');
const TestAllTypesProto2 = goog.require('proto.conformance.TestAllTypesProto2');
const TestAllTypesProto3 = goog.require('proto.conformance.TestAllTypesProto3');
const WireFormat = goog.require('proto.conformance.WireFormat');
const base64 = goog.require('goog.crypt.base64');
/**
* Creates a `proto.conformance.ConformanceResponse` response according to the
* `proto.conformance.ConformanceRequest` request.
* @param {!ConformanceRequest} request
* @return {!ConformanceResponse} response
*/
function doTest(request) {
const response = ConformanceResponse.createEmpty();
if(request.getPayloadCase() === ConformanceRequest.PayloadCase.JSON_PAYLOAD) {
response.setSkipped('Json is not supported as input format.');
return response;
}
if(request.getPayloadCase() === ConformanceRequest.PayloadCase.TEXT_PAYLOAD) {
response.setSkipped('Text format is not supported as input format.');
return response;
}
if(request.getPayloadCase() === ConformanceRequest.PayloadCase.PAYLOAD_NOT_SET) {
response.setRuntimeError('Request didn\'t have payload.');
return response;
}
if(request.getPayloadCase() !== ConformanceRequest.PayloadCase.PROTOBUF_PAYLOAD) {
throw new Error('Request didn\'t have accepted input format.');
}
if (request.getRequestedOutputFormat() === WireFormat.JSON) {
response.setSkipped('Json is not supported as output format.');
return response;
}
if (request.getRequestedOutputFormat() === WireFormat.TEXT_FORMAT) {
response.setSkipped('Text format is not supported as output format.');
return response;
}
if (request.getRequestedOutputFormat() === WireFormat.TEXT_FORMAT) {
response.setRuntimeError('Unspecified output format');
return response;
}
if (request.getRequestedOutputFormat() !== WireFormat.PROTOBUF) {
throw new Error('Request didn\'t have accepted output format.');
}
if (request.getMessageType() === 'conformance.FailureSet') {
response.setProtobufPayload(new ArrayBuffer(0));
} else if (
request.getMessageType() ===
'protobuf_test_messages.proto2.TestAllTypesProto2') {
try {
const testMessage =
TestAllTypesProto2.deserialize(request.getProtobufPayload());
response.setProtobufPayload(testMessage.serialize());
} catch (err) {
response.setParseError(err.toString());
}
} else if (
request.getMessageType() ===
'protobuf_test_messages.proto3.TestAllTypesProto3') {
try {
const testMessage =
TestAllTypesProto3.deserialize(request.getProtobufPayload());
response.setProtobufPayload(testMessage.serialize());
} catch (err) {
response.setParseError(err.toString());
}
} else {
throw new Error(
`Payload message not supported: ${request.getMessageType()}.`);
}
return response;
}
/**
* Same as doTest, but both request and response are in base64.
* @param {string} base64Request
* @return {string} response
*/
function runConformanceTest(base64Request) {
const request =
ConformanceRequest.deserialize(
base64.decodeStringToUint8Array(base64Request).buffer);
const response = doTest(request);
return base64.encodeByteArray(new Uint8Array(response.serialize()));
}
// Needed for node test
exports.doTest = doTest;
// Needed for browser test
goog.exportSymbol('runConformanceTest', runConformanceTest);

View File

@ -0,0 +1,62 @@
const ConformanceRequest = goog.require('proto.conformance.ConformanceRequest');
const {doTest} = goog.require('javascript.protobuf.conformance');
const fs = require('fs');
/**
* Reads a buffer of N bytes.
* @param {number} bytes Number of bytes to read.
* @return {!Buffer} Buffer which contains data.
*/
function readBuffer(bytes) {
// Linux cannot use process.stdin.fd (which isn't set up as sync)
const buf = new Buffer.alloc(bytes);
const fd = fs.openSync('/dev/stdin', 'r');
fs.readSync(fd, buf, 0, bytes);
fs.closeSync(fd);
return buf;
}
/**
* Writes all data in buffer.
* @param {!Buffer} buffer Buffer which contains data.
*/
function writeBuffer(buffer) {
// Under linux, process.stdout.fd is async. Needs to open stdout in a synced
// way for sync write.
const fd = fs.openSync('/dev/stdout', 'w');
fs.writeSync(fd, buffer, 0, buffer.length);
fs.closeSync(fd);
}
/**
* Returns true if the test ran successfully, false on legitimate EOF.
* @return {boolean} Whether to continue test.
*/
function runConformanceTest() {
const requestLengthBuf = readBuffer(4);
const requestLength = requestLengthBuf.readInt32LE(0);
if (!requestLength) {
return false;
}
const serializedRequest = readBuffer(requestLength);
const array = new Uint8Array(serializedRequest);
const request = ConformanceRequest.deserialize(array.buffer);
const response = doTest(request);
const serializedResponse = response.serialize();
const responseLengthBuf = new Buffer.alloc(4);
responseLengthBuf.writeInt32LE(serializedResponse.byteLength, 0);
writeBuffer(responseLengthBuf);
writeBuffer(new Buffer.from(serializedResponse));
return true;
}
while (true) {
if (!runConformanceTest()) {
break;
}
}

View File

@ -0,0 +1,309 @@
/**
* @fileoverview Handwritten code of TestAllTypesProto2.
*/
goog.module('proto.conformance.TestAllTypesProto2');
const InternalMessage = goog.require('protobuf.binary.InternalMessage');
const Kernel = goog.require('protobuf.runtime.Kernel');
/**
* Handwritten code of conformance.TestAllTypesProto2.
* Check google/protobuf/test_messages_proto3.proto for more details.
* @implements {InternalMessage}
* @final
*/
class TestAllTypesProto2 {
/**
* @param {!Kernel=} accessor
* @private
*/
constructor(accessor = Kernel.createEmpty()) {
/** @private @const {!Kernel} */
this.accessor_ = accessor;
}
/**
* @override
* @package
* @return {!Kernel}
*/
internalGetKernel() {
return this.accessor_;
}
/**
* Create a request instance with the given bytes data.
* If we directly use the accessor created by the binary decoding, the
* Kernel instance will only copy the same data over for encoding. By
* explicitly fetching data from the previous accessor and setting all fields
* into a new accessor, we will actually test encoding/decoding for the binary
* format.
* @param {!ArrayBuffer} bytes
* @return {!TestAllTypesProto2}
*/
static deserialize(bytes) {
const msg = new TestAllTypesProto2();
const requestAccessor = Kernel.fromArrayBuffer(bytes);
if (requestAccessor.hasFieldNumber(1)) {
const value = requestAccessor.getInt32WithDefault(1);
msg.accessor_.setInt32(1, value);
}
if (requestAccessor.hasFieldNumber(2)) {
const value = requestAccessor.getInt64WithDefault(2);
msg.accessor_.setInt64(2, value);
}
if (requestAccessor.hasFieldNumber(3)) {
const value = requestAccessor.getUint32WithDefault(3);
msg.accessor_.setUint32(3, value);
}
if (requestAccessor.hasFieldNumber(4)) {
const value = requestAccessor.getUint64WithDefault(4);
msg.accessor_.setUint64(4, value);
}
if (requestAccessor.hasFieldNumber(5)) {
const value = requestAccessor.getSint32WithDefault(5);
msg.accessor_.setSint32(5, value);
}
if (requestAccessor.hasFieldNumber(6)) {
const value = requestAccessor.getSint64WithDefault(6);
msg.accessor_.setSint64(6, value);
}
if (requestAccessor.hasFieldNumber(7)) {
const value = requestAccessor.getFixed32WithDefault(7);
msg.accessor_.setFixed32(7, value);
}
if (requestAccessor.hasFieldNumber(8)) {
const value = requestAccessor.getFixed64WithDefault(8);
msg.accessor_.setFixed64(8, value);
}
if (requestAccessor.hasFieldNumber(9)) {
const value = requestAccessor.getSfixed32WithDefault(9);
msg.accessor_.setSfixed32(9, value);
}
if (requestAccessor.hasFieldNumber(10)) {
const value = requestAccessor.getSfixed64WithDefault(10);
msg.accessor_.setSfixed64(10, value);
}
if (requestAccessor.hasFieldNumber(11)) {
const value = requestAccessor.getFloatWithDefault(11);
msg.accessor_.setFloat(11, value);
}
if (requestAccessor.hasFieldNumber(12)) {
const value = requestAccessor.getDoubleWithDefault(12);
msg.accessor_.setDouble(12, value);
}
if (requestAccessor.hasFieldNumber(13)) {
const value = requestAccessor.getBoolWithDefault(13);
msg.accessor_.setBool(13, value);
}
if (requestAccessor.hasFieldNumber(14)) {
const value = requestAccessor.getStringWithDefault(14);
msg.accessor_.setString(14, value);
}
if (requestAccessor.hasFieldNumber(15)) {
const value = requestAccessor.getBytesWithDefault(15);
msg.accessor_.setBytes(15, value);
}
if (requestAccessor.hasFieldNumber(18)) {
const value = requestAccessor.getMessage(
18, (accessor) => new TestAllTypesProto2(accessor));
msg.accessor_.setMessage(18, value);
}
if (requestAccessor.hasFieldNumber(21)) {
// Unknown enum is not checked here, because even if an enum is unknown,
// it should be kept during encoding. For the purpose of wire format test,
// we can simplify the implementation by treating it as an int32 field,
// which has the same semantic except for the unknown value checking.
const value = requestAccessor.getInt32WithDefault(21);
msg.accessor_.setInt32(21, value);
}
if (requestAccessor.hasFieldNumber(31)) {
const value = requestAccessor.getRepeatedInt32Iterable(31);
msg.accessor_.setUnpackedInt32Iterable(31, value);
}
if (requestAccessor.hasFieldNumber(32)) {
const value = requestAccessor.getRepeatedInt64Iterable(32);
msg.accessor_.setUnpackedInt64Iterable(32, value);
}
if (requestAccessor.hasFieldNumber(33)) {
const value = requestAccessor.getRepeatedUint32Iterable(33);
msg.accessor_.setUnpackedUint32Iterable(33, value);
}
if (requestAccessor.hasFieldNumber(34)) {
const value = requestAccessor.getRepeatedUint64Iterable(34);
msg.accessor_.setUnpackedUint64Iterable(34, value);
}
if (requestAccessor.hasFieldNumber(35)) {
const value = requestAccessor.getRepeatedSint32Iterable(35);
msg.accessor_.setUnpackedSint32Iterable(35, value);
}
if (requestAccessor.hasFieldNumber(36)) {
const value = requestAccessor.getRepeatedSint64Iterable(36);
msg.accessor_.setUnpackedSint64Iterable(36, value);
}
if (requestAccessor.hasFieldNumber(37)) {
const value = requestAccessor.getRepeatedFixed32Iterable(37);
msg.accessor_.setUnpackedFixed32Iterable(37, value);
}
if (requestAccessor.hasFieldNumber(38)) {
const value = requestAccessor.getRepeatedFixed64Iterable(38);
msg.accessor_.setUnpackedFixed64Iterable(38, value);
}
if (requestAccessor.hasFieldNumber(39)) {
const value = requestAccessor.getRepeatedSfixed32Iterable(39);
msg.accessor_.setUnpackedSfixed32Iterable(39, value);
}
if (requestAccessor.hasFieldNumber(40)) {
const value = requestAccessor.getRepeatedSfixed64Iterable(40);
msg.accessor_.setUnpackedSfixed64Iterable(40, value);
}
if (requestAccessor.hasFieldNumber(41)) {
const value = requestAccessor.getRepeatedFloatIterable(41);
msg.accessor_.setUnpackedFloatIterable(41, value);
}
if (requestAccessor.hasFieldNumber(42)) {
const value = requestAccessor.getRepeatedDoubleIterable(42);
msg.accessor_.setUnpackedDoubleIterable(42, value);
}
if (requestAccessor.hasFieldNumber(43)) {
const value = requestAccessor.getRepeatedBoolIterable(43);
msg.accessor_.setUnpackedBoolIterable(43, value);
}
if (requestAccessor.hasFieldNumber(44)) {
const value = requestAccessor.getRepeatedStringIterable(44);
msg.accessor_.setRepeatedStringIterable(44, value);
}
if (requestAccessor.hasFieldNumber(45)) {
const value = requestAccessor.getRepeatedBytesIterable(45);
msg.accessor_.setRepeatedBytesIterable(45, value);
}
if (requestAccessor.hasFieldNumber(48)) {
const value = requestAccessor.getRepeatedMessageIterable(
48, (accessor) => new TestAllTypesProto2(accessor));
msg.accessor_.setRepeatedMessageIterable(48, value);
}
if (requestAccessor.hasFieldNumber(51)) {
// Unknown enum is not checked here, because even if an enum is unknown,
// it should be kept during encoding. For the purpose of wire format test,
// we can simplify the implementation by treating it as an int32 field,
// which has the same semantic except for the unknown value checking.
const value = requestAccessor.getRepeatedInt32Iterable(51);
msg.accessor_.setUnpackedInt32Iterable(51, value);
}
if (requestAccessor.hasFieldNumber(75)) {
const value = requestAccessor.getRepeatedInt32Iterable(75);
msg.accessor_.setPackedInt32Iterable(75, value);
}
if (requestAccessor.hasFieldNumber(76)) {
const value = requestAccessor.getRepeatedInt64Iterable(76);
msg.accessor_.setPackedInt64Iterable(76, value);
}
if (requestAccessor.hasFieldNumber(77)) {
const value = requestAccessor.getRepeatedUint32Iterable(77);
msg.accessor_.setPackedUint32Iterable(77, value);
}
if (requestAccessor.hasFieldNumber(78)) {
const value = requestAccessor.getRepeatedUint64Iterable(78);
msg.accessor_.setPackedUint64Iterable(78, value);
}
if (requestAccessor.hasFieldNumber(79)) {
const value = requestAccessor.getRepeatedSint32Iterable(79);
msg.accessor_.setPackedSint32Iterable(79, value);
}
if (requestAccessor.hasFieldNumber(80)) {
const value = requestAccessor.getRepeatedSint64Iterable(80);
msg.accessor_.setPackedSint64Iterable(80, value);
}
if (requestAccessor.hasFieldNumber(81)) {
const value = requestAccessor.getRepeatedFixed32Iterable(81);
msg.accessor_.setPackedFixed32Iterable(81, value);
}
if (requestAccessor.hasFieldNumber(82)) {
const value = requestAccessor.getRepeatedFixed64Iterable(82);
msg.accessor_.setPackedFixed64Iterable(82, value);
}
if (requestAccessor.hasFieldNumber(83)) {
const value = requestAccessor.getRepeatedSfixed32Iterable(83);
msg.accessor_.setPackedSfixed32Iterable(83, value);
}
if (requestAccessor.hasFieldNumber(84)) {
const value = requestAccessor.getRepeatedSfixed64Iterable(84);
msg.accessor_.setPackedSfixed64Iterable(84, value);
}
if (requestAccessor.hasFieldNumber(85)) {
const value = requestAccessor.getRepeatedFloatIterable(85);
msg.accessor_.setPackedFloatIterable(85, value);
}
if (requestAccessor.hasFieldNumber(86)) {
const value = requestAccessor.getRepeatedDoubleIterable(86);
msg.accessor_.setPackedDoubleIterable(86, value);
}
if (requestAccessor.hasFieldNumber(87)) {
const value = requestAccessor.getRepeatedBoolIterable(87);
msg.accessor_.setPackedBoolIterable(87, value);
}
if (requestAccessor.hasFieldNumber(88)) {
const value = requestAccessor.getRepeatedInt32Iterable(88);
msg.accessor_.setPackedInt32Iterable(88, value);
}
return msg;
}
/**
* Serializes into binary data.
* @return {!ArrayBuffer}
*/
serialize() {
return this.accessor_.serialize();
}
}
exports = TestAllTypesProto2;

View File

@ -0,0 +1,310 @@
/**
* @fileoverview Handwritten code of TestAllTypesProto3.
*/
goog.module('proto.conformance.TestAllTypesProto3');
const InternalMessage = goog.require('protobuf.binary.InternalMessage');
const Kernel = goog.require('protobuf.runtime.Kernel');
/**
* Handwritten code of conformance.TestAllTypesProto3.
* Check google/protobuf/test_messages_proto3.proto for more details.
* @implements {InternalMessage}
* @final
*/
class TestAllTypesProto3 {
/**
* @param {!Kernel=} accessor
* @private
*/
constructor(accessor = Kernel.createEmpty()) {
/** @private @const {!Kernel} */
this.accessor_ = accessor;
}
/**
* @override
* @package
* @return {!Kernel}
*/
internalGetKernel() {
return this.accessor_;
}
/**
* Create a request instance with the given bytes data.
* If we directly use the accessor created by the binary decoding, the
* Kernel instance will only copy the same data over for encoding. By
* explicitly fetching data from the previous accessor and setting all fields
* into a new accessor, we will actually test encoding/decoding for the binary
* format.
* @param {!ArrayBuffer} bytes
* @return {!TestAllTypesProto3}
*/
static deserialize(bytes) {
const msg = new TestAllTypesProto3();
const requestAccessor = Kernel.fromArrayBuffer(bytes);
if (requestAccessor.hasFieldNumber(1)) {
const value = requestAccessor.getInt32WithDefault(1);
msg.accessor_.setInt32(1, value);
}
if (requestAccessor.hasFieldNumber(2)) {
const value = requestAccessor.getInt64WithDefault(2);
msg.accessor_.setInt64(2, value);
}
if (requestAccessor.hasFieldNumber(3)) {
const value = requestAccessor.getUint32WithDefault(3);
msg.accessor_.setUint32(3, value);
}
if (requestAccessor.hasFieldNumber(4)) {
const value = requestAccessor.getUint64WithDefault(4);
msg.accessor_.setUint64(4, value);
}
if (requestAccessor.hasFieldNumber(5)) {
const value = requestAccessor.getSint32WithDefault(5);
msg.accessor_.setSint32(5, value);
}
if (requestAccessor.hasFieldNumber(6)) {
const value = requestAccessor.getSint64WithDefault(6);
msg.accessor_.setSint64(6, value);
}
if (requestAccessor.hasFieldNumber(7)) {
const value = requestAccessor.getFixed32WithDefault(7);
msg.accessor_.setFixed32(7, value);
}
if (requestAccessor.hasFieldNumber(8)) {
const value = requestAccessor.getFixed64WithDefault(8);
msg.accessor_.setFixed64(8, value);
}
if (requestAccessor.hasFieldNumber(9)) {
const value = requestAccessor.getSfixed32WithDefault(9);
msg.accessor_.setSfixed32(9, value);
}
if (requestAccessor.hasFieldNumber(10)) {
const value = requestAccessor.getSfixed64WithDefault(10);
msg.accessor_.setSfixed64(10, value);
}
if (requestAccessor.hasFieldNumber(11)) {
const value = requestAccessor.getFloatWithDefault(11);
msg.accessor_.setFloat(11, value);
}
if (requestAccessor.hasFieldNumber(12)) {
const value = requestAccessor.getDoubleWithDefault(12);
msg.accessor_.setDouble(12, value);
}
if (requestAccessor.hasFieldNumber(13)) {
const value = requestAccessor.getBoolWithDefault(13);
msg.accessor_.setBool(13, value);
}
if (requestAccessor.hasFieldNumber(14)) {
const value = requestAccessor.getStringWithDefault(14);
msg.accessor_.setString(14, value);
}
if (requestAccessor.hasFieldNumber(15)) {
const value = requestAccessor.getBytesWithDefault(15);
msg.accessor_.setBytes(15, value);
}
if (requestAccessor.hasFieldNumber(18)) {
const value = requestAccessor.getMessage(
18, (accessor) => new TestAllTypesProto3(accessor));
msg.accessor_.setMessage(18, value);
}
if (requestAccessor.hasFieldNumber(21)) {
// Unknown enum is not checked here, because even if an enum is unknown,
// it should be kept during encoding. For the purpose of wire format test,
// we can simplify the implementation by treating it as an int32 field,
// which has the same semantic except for the unknown value checking.
const value = requestAccessor.getInt32WithDefault(21);
msg.accessor_.setInt32(21, value);
}
if (requestAccessor.hasFieldNumber(31)) {
const value = requestAccessor.getRepeatedInt32Iterable(31);
msg.accessor_.setPackedInt32Iterable(31, value);
}
if (requestAccessor.hasFieldNumber(32)) {
const value = requestAccessor.getRepeatedInt64Iterable(32);
msg.accessor_.setPackedInt64Iterable(32, value);
}
if (requestAccessor.hasFieldNumber(33)) {
const value = requestAccessor.getRepeatedUint32Iterable(33);
msg.accessor_.setPackedUint32Iterable(33, value);
}
if (requestAccessor.hasFieldNumber(34)) {
const value = requestAccessor.getRepeatedUint64Iterable(34);
msg.accessor_.setPackedUint64Iterable(34, value);
}
if (requestAccessor.hasFieldNumber(35)) {
const value = requestAccessor.getRepeatedSint32Iterable(35);
msg.accessor_.setPackedSint32Iterable(35, value);
}
if (requestAccessor.hasFieldNumber(36)) {
const value = requestAccessor.getRepeatedSint64Iterable(36);
msg.accessor_.setPackedSint64Iterable(36, value);
}
if (requestAccessor.hasFieldNumber(37)) {
const value = requestAccessor.getRepeatedFixed32Iterable(37);
msg.accessor_.setPackedFixed32Iterable(37, value);
}
if (requestAccessor.hasFieldNumber(38)) {
const value = requestAccessor.getRepeatedFixed64Iterable(38);
msg.accessor_.setPackedFixed64Iterable(38, value);
}
if (requestAccessor.hasFieldNumber(39)) {
const value = requestAccessor.getRepeatedSfixed32Iterable(39);
msg.accessor_.setPackedSfixed32Iterable(39, value);
}
if (requestAccessor.hasFieldNumber(40)) {
const value = requestAccessor.getRepeatedSfixed64Iterable(40);
msg.accessor_.setPackedSfixed64Iterable(40, value);
}
if (requestAccessor.hasFieldNumber(41)) {
const value = requestAccessor.getRepeatedFloatIterable(41);
msg.accessor_.setPackedFloatIterable(41, value);
}
if (requestAccessor.hasFieldNumber(42)) {
const value = requestAccessor.getRepeatedDoubleIterable(42);
msg.accessor_.setPackedDoubleIterable(42, value);
}
if (requestAccessor.hasFieldNumber(43)) {
const value = requestAccessor.getRepeatedBoolIterable(43);
msg.accessor_.setPackedBoolIterable(43, value);
}
if (requestAccessor.hasFieldNumber(44)) {
const value = requestAccessor.getRepeatedStringIterable(44);
msg.accessor_.setRepeatedStringIterable(44, value);
}
if (requestAccessor.hasFieldNumber(45)) {
const value = requestAccessor.getRepeatedBytesIterable(45);
msg.accessor_.setRepeatedBytesIterable(45, value);
}
if (requestAccessor.hasFieldNumber(48)) {
const value = requestAccessor.getRepeatedMessageIterable(
48, (accessor) => new TestAllTypesProto3(accessor));
msg.accessor_.setRepeatedMessageIterable(48, value);
}
if (requestAccessor.hasFieldNumber(51)) {
// Unknown enum is not checked here, because even if an enum is unknown,
// it should be kept during encoding. For the purpose of wire format test,
// we can simplify the implementation by treating it as an int32 field,
// which has the same semantic except for the unknown value checking.
const value = requestAccessor.getRepeatedInt32Iterable(51);
msg.accessor_.setPackedInt32Iterable(51, value);
}
if (requestAccessor.hasFieldNumber(89)) {
const value = requestAccessor.getRepeatedInt32Iterable(89);
msg.accessor_.setUnpackedInt32Iterable(89, value);
}
if (requestAccessor.hasFieldNumber(90)) {
const value = requestAccessor.getRepeatedInt64Iterable(90);
msg.accessor_.setUnpackedInt64Iterable(90, value);
}
if (requestAccessor.hasFieldNumber(91)) {
const value = requestAccessor.getRepeatedUint32Iterable(91);
msg.accessor_.setUnpackedUint32Iterable(91, value);
}
if (requestAccessor.hasFieldNumber(92)) {
const value = requestAccessor.getRepeatedUint64Iterable(92);
msg.accessor_.setUnpackedUint64Iterable(92, value);
}
if (requestAccessor.hasFieldNumber(93)) {
const value = requestAccessor.getRepeatedSint32Iterable(93);
msg.accessor_.setUnpackedSint32Iterable(93, value);
}
if (requestAccessor.hasFieldNumber(94)) {
const value = requestAccessor.getRepeatedSint64Iterable(94);
msg.accessor_.setUnpackedSint64Iterable(94, value);
}
if (requestAccessor.hasFieldNumber(95)) {
const value = requestAccessor.getRepeatedFixed32Iterable(95);
msg.accessor_.setUnpackedFixed32Iterable(95, value);
}
if (requestAccessor.hasFieldNumber(96)) {
const value = requestAccessor.getRepeatedFixed64Iterable(96);
msg.accessor_.setUnpackedFixed64Iterable(96, value);
}
if (requestAccessor.hasFieldNumber(97)) {
const value = requestAccessor.getRepeatedSfixed32Iterable(97);
msg.accessor_.setUnpackedSfixed32Iterable(97, value);
}
if (requestAccessor.hasFieldNumber(98)) {
const value = requestAccessor.getRepeatedSfixed64Iterable(98);
msg.accessor_.setUnpackedSfixed64Iterable(98, value);
}
if (requestAccessor.hasFieldNumber(99)) {
const value = requestAccessor.getRepeatedFloatIterable(99);
msg.accessor_.setUnpackedFloatIterable(99, value);
}
if (requestAccessor.hasFieldNumber(100)) {
const value = requestAccessor.getRepeatedDoubleIterable(100);
msg.accessor_.setUnpackedDoubleIterable(100, value);
}
if (requestAccessor.hasFieldNumber(101)) {
const value = requestAccessor.getRepeatedBoolIterable(101);
msg.accessor_.setUnpackedBoolIterable(101, value);
}
if (requestAccessor.hasFieldNumber(102)) {
const value = requestAccessor.getRepeatedInt32Iterable(102);
msg.accessor_.setUnpackedInt32Iterable(102, value);
}
return msg;
}
/**
* Serializes into binary data.
* @return {!ArrayBuffer}
*/
serialize() {
return this.accessor_.serialize();
}
}
exports = TestAllTypesProto3;

View File

@ -0,0 +1,16 @@
/**
* @fileoverview Handwritten code of WireFormat.
*/
goog.module('proto.conformance.WireFormat');
/**
* @enum {number}
*/
const WireFormat = {
UNSPECIFIED: 0,
PROTOBUF: 1,
JSON: 2,
TEXT_FORMAT: 4,
};
exports = WireFormat;

View File

@ -0,0 +1,89 @@
/**
* @fileoverview Test data for double encoding and decoding.
*/
goog.module('protobuf.binary.doubleTestPairs');
const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
/**
* An array of Pairs of double values and their bit representation.
* This is used to test encoding and decoding from the protobuf wire format.
* @return {!Array<{name: string, doubleValue:number, bufferDecoder:
* !BufferDecoder}>}
*/
function getDoublePairs() {
const doublePairs = [
{
name: 'zero',
doubleValue: 0,
bufferDecoder:
createBufferDecoder(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
},
{
name: 'minus zero',
doubleValue: -0,
bufferDecoder:
createBufferDecoder(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80)
},
{
name: 'one',
doubleValue: 1,
bufferDecoder:
createBufferDecoder(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F)
},
{
name: 'minus one',
doubleValue: -1,
bufferDecoder:
createBufferDecoder(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xBF)
},
{
name: 'PI',
doubleValue: Math.PI,
bufferDecoder:
createBufferDecoder(0x18, 0x2D, 0x44, 0x54, 0xFB, 0x21, 0x09, 0x40)
},
{
name: 'max value',
doubleValue: Number.MAX_VALUE,
bufferDecoder:
createBufferDecoder(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0x7F)
},
{
name: 'min value',
doubleValue: Number.MIN_VALUE,
bufferDecoder:
createBufferDecoder(0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
},
{
name: 'Infinity',
doubleValue: Infinity,
bufferDecoder:
createBufferDecoder(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x7F)
},
{
name: 'minus Infinity',
doubleValue: -Infinity,
bufferDecoder:
createBufferDecoder(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF)
},
{
name: 'Number.MAX_SAFE_INTEGER',
doubleValue: Number.MAX_SAFE_INTEGER,
bufferDecoder:
createBufferDecoder(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0x43)
},
{
name: 'Number.MIN_SAFE_INTEGER',
doubleValue: Number.MIN_SAFE_INTEGER,
bufferDecoder:
createBufferDecoder(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0xC3)
},
];
return [...doublePairs];
}
exports = {getDoublePairs};

View File

@ -0,0 +1,196 @@
/**
* @fileoverview Contains classes that hold data for a protobuf field.
*/
goog.module('protobuf.binary.field');
const WireType = goog.requireType('protobuf.binary.WireType');
const Writer = goog.requireType('protobuf.binary.Writer');
const {checkDefAndNotNull, checkState} = goog.require('protobuf.internal.checks');
/**
* Number of bits taken to represent a wire type.
* @const {number}
*/
const WIRE_TYPE_LENGTH_BITS = 3;
/** @const {number} */
const WIRE_TYPE_EXTRACTOR = (1 << WIRE_TYPE_LENGTH_BITS) - 1;
/**
* An IndexEntry consists of the wire type and the position of a field in the
* binary data. The wire type and the position are encoded into a single number
* to save memory, which can be decoded using Field.getWireType() and
* Field.getStartIndex() methods.
* @typedef {number}
*/
let IndexEntry;
/**
* An entry containing the index into the binary data and/or the corresponding
* cached JS object(s) for a field.
* @template T
* @final
* @package
*/
class Field {
/**
* Creates a field and inserts the wireType and position of the first
* occurrence of a field.
* @param {!WireType} wireType
* @param {number} startIndex
* @return {!Field}
*/
static fromFirstIndexEntry(wireType, startIndex) {
return new Field([Field.encodeIndexEntry(wireType, startIndex)]);
}
/**
* @param {T} decodedValue The cached JS object decoded from the binary data.
* @param {function(!Writer, number, T):void|undefined} encoder Write function
* to encode the cache into binary bytes.
* @return {!Field}
* @template T
*/
static fromDecodedValue(decodedValue, encoder) {
return new Field(null, decodedValue, encoder);
}
/**
* @param {!WireType} wireType
* @param {number} startIndex
* @return {!IndexEntry}
*/
static encodeIndexEntry(wireType, startIndex) {
return startIndex << WIRE_TYPE_LENGTH_BITS | wireType;
}
/**
* @param {!IndexEntry} indexEntry
* @return {!WireType}
*/
static getWireType(indexEntry) {
return /** @type {!WireType} */ (indexEntry & WIRE_TYPE_EXTRACTOR);
}
/**
* @param {!IndexEntry} indexEntry
* @return {number}
*/
static getStartIndex(indexEntry) {
return indexEntry >> WIRE_TYPE_LENGTH_BITS;
}
/**
* @param {?Array<!IndexEntry>} indexArray
* @param {T=} decodedValue
* @param {function(!Writer, number, T):void=} encoder
* @private
*/
constructor(indexArray, decodedValue = undefined, encoder = undefined) {
checkState(
!!indexArray || decodedValue !== undefined,
'At least one of indexArray and decodedValue must be set');
/** @private {?Array<!IndexEntry>} */
this.indexArray_ = indexArray;
/** @private {T|undefined} */
this.decodedValue_ = decodedValue;
// TODO: Consider storing an enum to represent encoder
/** @private {function(!Writer, number, T)|undefined} */
this.encoder_ = encoder;
}
/**
* Adds a new IndexEntry.
* @param {!WireType} wireType
* @param {number} startIndex
*/
addIndexEntry(wireType, startIndex) {
checkDefAndNotNull(this.indexArray_)
.push(Field.encodeIndexEntry(wireType, startIndex));
}
/**
* Returns the array of IndexEntry.
* @return {?Array<!IndexEntry>}
*/
getIndexArray() {
return this.indexArray_;
}
/**
* Caches the decoded value and sets the write function to encode cache into
* binary bytes.
* @param {T} decodedValue
* @param {function(!Writer, number, T):void|undefined} encoder
*/
setCache(decodedValue, encoder) {
this.decodedValue_ = decodedValue;
this.encoder_ = encoder;
this.maybeRemoveIndexArray_();
}
/**
* If the decoded value has been set.
* @return {boolean}
*/
hasDecodedValue() {
return this.decodedValue_ !== undefined;
}
/**
* Returns the cached decoded value. The value needs to be set when this
* method is called.
* @return {T}
*/
getDecodedValue() {
// Makes sure that the decoded value in the cache has already been set. This
// prevents callers from doing `if (field.getDecodedValue()) {...}` to check
// if a value exist in the cache, because the check might return false even
// if the cache has a valid value set (e.g. 0 or empty string).
checkState(this.decodedValue_ !== undefined);
return this.decodedValue_;
}
/**
* Returns the write function to encode cache into binary bytes.
* @return {function(!Writer, number, T)|undefined}
*/
getEncoder() {
return this.encoder_;
}
/**
* Returns a copy of the field, containing the original index entries and a
* shallow copy of the cache.
* @return {!Field}
*/
shallowCopy() {
// Repeated fields are arrays in the cache.
// We have to copy the array to make sure that modifications to a repeated
// field (e.g. add) are not seen on a cloned accessor.
const copiedCache = this.hasDecodedValue() ?
(Array.isArray(this.getDecodedValue()) ? [...this.getDecodedValue()] :
this.getDecodedValue()) :
undefined;
return new Field(this.getIndexArray(), copiedCache, this.getEncoder());
}
/**
* @private
*/
maybeRemoveIndexArray_() {
checkState(
this.encoder_ === undefined || this.decodedValue_ !== undefined,
'Encoder exists but decoded value doesn\'t');
if (this.encoder_ !== undefined) {
this.indexArray_ = null;
}
}
}
exports = {
IndexEntry,
Field,
};

View File

@ -0,0 +1,36 @@
/**
* @fileoverview Test data for float encoding and decoding.
*/
goog.module('protobuf.binary.fixed32TestPairs');
const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
/**
* An array of Pairs of float values and their bit representation.
* This is used to test encoding and decoding from/to the protobuf wire format.
* @return {!Array<{name: string, intValue: number, bufferDecoder:
* !BufferDecoder}>}
*/
function getFixed32Pairs() {
const fixed32Pairs = [
{
name: 'zero',
intValue: 0,
bufferDecoder: createBufferDecoder(0x00, 0x00, 0x00, 0x00),
},
{
name: 'one ',
intValue: 1,
bufferDecoder: createBufferDecoder(0x01, 0x00, 0x00, 0x00)
},
{
name: 'max int 2^32 -1',
intValue: Math.pow(2, 32) - 1,
bufferDecoder: createBufferDecoder(0xFF, 0xFF, 0xFF, 0xFF)
},
];
return [...fixed32Pairs];
}
exports = {getFixed32Pairs};

View File

@ -0,0 +1,78 @@
/**
* @fileoverview Test data for float encoding and decoding.
*/
goog.module('protobuf.binary.floatTestPairs');
const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
/**
* An array of Pairs of float values and their bit representation.
* This is used to test encoding and decoding from/to the protobuf wire format.
* @return {!Array<{name: string, floatValue:number, bufferDecoder:
* !BufferDecoder}>}
*/
function getFloatPairs() {
const floatPairs = [
{
name: 'zero',
floatValue: 0,
bufferDecoder: createBufferDecoder(0x00, 0x00, 0x00, 0x00),
},
{
name: 'minus zero',
floatValue: -0,
bufferDecoder: createBufferDecoder(0x00, 0x00, 0x00, 0x80)
},
{
name: 'one ',
floatValue: 1,
bufferDecoder: createBufferDecoder(0x00, 0x00, 0x80, 0x3F)
},
{
name: 'minus one',
floatValue: -1,
bufferDecoder: createBufferDecoder(0x00, 0x00, 0x80, 0xBF)
},
{
name: 'two',
floatValue: 2,
bufferDecoder: createBufferDecoder(0x00, 0x00, 0x00, 0x40)
},
{
name: 'max float32',
floatValue: Math.pow(2, 127) * (2 - 1 / Math.pow(2, 23)),
bufferDecoder: createBufferDecoder(0xFF, 0xFF, 0x7F, 0x7F)
},
{
name: 'min float32',
floatValue: 1 / Math.pow(2, 127 - 1),
bufferDecoder: createBufferDecoder(0x00, 0x00, 0x80, 0x00)
},
{
name: 'Infinity',
floatValue: Infinity,
bufferDecoder: createBufferDecoder(0x00, 0x00, 0x80, 0x7F)
},
{
name: 'minus Infinity',
floatValue: -Infinity,
bufferDecoder: createBufferDecoder(0x00, 0x00, 0x80, 0xFF)
},
{
name: '1.5',
floatValue: 1.5,
bufferDecoder: createBufferDecoder(0x00, 0x00, 0xC0, 0x3F)
},
{
name: '1.6',
floatValue: 1.6,
bufferDecoder: createBufferDecoder(0xCD, 0xCC, 0xCC, 0x3F)
},
];
return [...floatPairs];
}
exports = {getFloatPairs};

View File

@ -0,0 +1,55 @@
/**
* @fileoverview Utilities to index a binary proto by fieldnumbers without
* relying on strutural proto information.
*/
goog.module('protobuf.binary.indexer');
const BinaryStorage = goog.require('protobuf.runtime.BinaryStorage');
const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
const WireType = goog.require('protobuf.binary.WireType');
const {Field} = goog.require('protobuf.binary.field');
const {checkCriticalState} = goog.require('protobuf.internal.checks');
const {skipField, tagToFieldNumber, tagToWireType} = goog.require('protobuf.binary.tag');
/**
* Appends a new entry in the index array for the given field number.
* @param {!BinaryStorage<!Field>} storage
* @param {number} fieldNumber
* @param {!WireType} wireType
* @param {number} startIndex
*/
function addIndexEntry(storage, fieldNumber, wireType, startIndex) {
const field = storage.get(fieldNumber);
if (field !== undefined) {
field.addIndexEntry(wireType, startIndex);
} else {
storage.set(fieldNumber, Field.fromFirstIndexEntry(wireType, startIndex));
}
}
/**
* Creates an index of field locations in a given binary protobuf.
* @param {!BufferDecoder} bufferDecoder
* @param {number|undefined} pivot
* @return {!BinaryStorage<!Field>}
* @package
*/
function buildIndex(bufferDecoder, pivot) {
bufferDecoder.setCursor(bufferDecoder.startIndex());
const storage = new BinaryStorage(pivot);
while (bufferDecoder.hasNext()) {
const tag = bufferDecoder.getUnsignedVarint32();
const wireType = tagToWireType(tag);
const fieldNumber = tagToFieldNumber(tag);
checkCriticalState(fieldNumber > 0, `Invalid field number ${fieldNumber}`);
addIndexEntry(storage, fieldNumber, wireType, bufferDecoder.cursor());
skipField(bufferDecoder, wireType, fieldNumber);
}
return storage;
}
exports = {
buildIndex,
tagToWireType,
};

View File

@ -0,0 +1,334 @@
/**
* @fileoverview Tests for indexer.js.
*/
goog.module('protobuf.binary.IndexerTest');
goog.setTestOnly();
// Note to the reader:
// Since the index behavior changes with the checking level some of the tests
// in this file have to know which checking level is enabled to make correct
// assertions.
// Test are run in all checking levels.
const BinaryStorage = goog.require('protobuf.runtime.BinaryStorage');
const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
const WireType = goog.require('protobuf.binary.WireType');
const {CHECK_CRITICAL_STATE} = goog.require('protobuf.internal.checks');
const {Field, IndexEntry} = goog.require('protobuf.binary.field');
const {buildIndex} = goog.require('protobuf.binary.indexer');
const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
/**
* Returns the number of fields stored.
*
* @param {!BinaryStorage} storage
* @return {number}
*/
function getStorageSize(storage) {
let size = 0;
storage.forEach(() => void size++);
return size;
}
/**
* @type {number}
*/
const PIVOT = 1;
/**
* Asserts a single IndexEntry at a given field number.
* @param {!BinaryStorage} storage
* @param {number} fieldNumber
* @param {...!IndexEntry} expectedEntries
*/
function assertStorageEntries(storage, fieldNumber, ...expectedEntries) {
expect(getStorageSize(storage)).toBe(1);
const entryArray = storage.get(fieldNumber).getIndexArray();
expect(entryArray).not.toBeUndefined();
expect(entryArray.length).toBe(expectedEntries.length);
for (let i = 0; i < entryArray.length; i++) {
const storageEntry = entryArray[i];
const expectedEntry = expectedEntries[i];
expect(storageEntry).toBe(expectedEntry);
}
}
describe('Indexer does', () => {
it('return empty storage for empty array', () => {
const storage = buildIndex(createBufferDecoder(), PIVOT);
expect(storage).not.toBeNull();
expect(getStorageSize(storage)).toBe(0);
});
it('throw for null array', () => {
expect(
() => buildIndex(
/** @type {!BufferDecoder} */ (/** @type {*} */ (null)), PIVOT))
.toThrow();
});
it('fail for invalid wire type (6)', () => {
expect(() => buildIndex(createBufferDecoder(0x0E, 0x01), PIVOT))
.toThrowError('Unexpected wire type: 6');
});
it('fail for invalid wire type (7)', () => {
expect(() => buildIndex(createBufferDecoder(0x0F, 0x01), PIVOT))
.toThrowError('Unexpected wire type: 7');
});
it('index varint', () => {
const data = createBufferDecoder(0x08, 0x01, 0x08, 0x01);
const storage = buildIndex(data, PIVOT);
assertStorageEntries(
storage, /* fieldNumber= */ 1,
Field.encodeIndexEntry(WireType.VARINT, /* startIndex= */ 1),
Field.encodeIndexEntry(WireType.VARINT, /* startIndex= */ 3));
});
it('index varint with two bytes field number', () => {
const data = createBufferDecoder(0xF8, 0x01, 0x01);
const storage = buildIndex(data, PIVOT);
assertStorageEntries(
storage, /* fieldNumber= */ 31,
Field.encodeIndexEntry(WireType.VARINT, /* startIndex= */ 2));
});
it('fail for varints that are longer than 10 bytes', () => {
const data = createBufferDecoder(
0x08, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00);
if (CHECK_CRITICAL_STATE) {
expect(() => buildIndex(data, PIVOT))
.toThrowError('Index out of bounds: index: 12 size: 11');
} else {
// Note in unchecked mode we produce invalid output for invalid inputs.
// This test just documents our behavior in those cases.
// These values might change at any point and are not considered
// what the implementation should be doing here.
const storage = buildIndex(data, PIVOT);
assertStorageEntries(
storage, /* fieldNumber= */ 1,
Field.encodeIndexEntry(WireType.VARINT, /* startIndex= */ 1));
}
});
it('fail for varints with no data', () => {
const data = createBufferDecoder(0x08);
expect(() => buildIndex(data, PIVOT)).toThrow();
});
it('index fixed64', () => {
const data = createBufferDecoder(
/* first= */ 0x09, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
/* second= */ 0x09, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08);
const storage = buildIndex(data, PIVOT);
assertStorageEntries(
storage, /* fieldNumber= */ 1,
Field.encodeIndexEntry(WireType.FIXED64, /* startIndex= */ 1),
Field.encodeIndexEntry(WireType.FIXED64, /* startIndex= */ 10));
});
it('fail for fixed64 data missing in input', () => {
const data =
createBufferDecoder(0x09, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07);
if (CHECK_CRITICAL_STATE) {
expect(() => buildIndex(data, PIVOT))
.toThrowError('Index out of bounds: index: 9 size: 8');
} else {
// Note in unchecked mode we produce invalid output for invalid inputs.
// This test just documents our behavior in those cases.
// These values might change at any point and are not considered
// what the implementation should be doing here.
const storage = buildIndex(data, PIVOT);
assertStorageEntries(
storage, /* fieldNumber= */ 1,
Field.encodeIndexEntry(WireType.FIXED64, /* startIndex= */ 1));
}
});
it('fail for fixed64 tag that has no data after it', () => {
if (CHECK_CRITICAL_STATE) {
const data = createBufferDecoder(0x09);
expect(() => buildIndex(data, PIVOT))
.toThrowError('Index out of bounds: index: 9 size: 1');
} else {
// Note in unchecked mode we produce invalid output for invalid inputs.
// This test just documents our behavior in those cases.
// These values might change at any point and are not considered
// what the implementation should be doing here.
const data = createBufferDecoder(0x09);
const storage = buildIndex(data, PIVOT);
assertStorageEntries(
storage, /* fieldNumber= */ 1,
Field.encodeIndexEntry(WireType.FIXED64, /* startIndex= */ 1));
}
});
it('index delimited', () => {
const data = createBufferDecoder(
/* first= */ 0x0A, 0x02, 0x00, 0x01, /* second= */ 0x0A, 0x02, 0x00,
0x01);
const storage = buildIndex(data, PIVOT);
assertStorageEntries(
storage, /* fieldNumber= */ 1,
Field.encodeIndexEntry(WireType.DELIMITED, /* startIndex= */ 1),
Field.encodeIndexEntry(WireType.DELIMITED, /* startIndex= */ 5));
});
it('fail for length deliimted field data missing in input', () => {
const data = createBufferDecoder(0x0A, 0x04, 0x00, 0x01);
if (CHECK_CRITICAL_STATE) {
expect(() => buildIndex(data, PIVOT))
.toThrowError('Index out of bounds: index: 6 size: 4');
} else {
// Note in unchecked mode we produce invalid output for invalid inputs.
// This test just documents our behavior in those cases.
// These values might change at any point and are not considered
// what the implementation should be doing here.
const storage = buildIndex(data, PIVOT);
assertStorageEntries(
storage, /* fieldNumber= */ 1,
Field.encodeIndexEntry(WireType.DELIMITED, /* startIndex= */ 1));
}
});
it('fail for delimited tag that has no data after it', () => {
const data = createBufferDecoder(0x0A);
expect(() => buildIndex(data, PIVOT)).toThrow();
});
it('index fixed32', () => {
const data = createBufferDecoder(
/* first= */ 0x0D, 0x01, 0x02, 0x03, 0x04, /* second= */ 0x0D, 0x01,
0x02, 0x03, 0x04);
const storage = buildIndex(data, PIVOT);
assertStorageEntries(
storage, /* fieldNumber= */ 1,
Field.encodeIndexEntry(WireType.FIXED32, /* startIndex= */ 1),
Field.encodeIndexEntry(WireType.FIXED32, /* startIndex= */ 6));
});
it('fail for fixed32 data missing in input', () => {
const data = createBufferDecoder(0x0D, 0x01, 0x02, 0x03);
if (CHECK_CRITICAL_STATE) {
expect(() => buildIndex(data, PIVOT))
.toThrowError('Index out of bounds: index: 5 size: 4');
} else {
// Note in unchecked mode we produce invalid output for invalid inputs.
// This test just documents our behavior in those cases.
// These values might change at any point and are not considered
// what the implementation should be doing here.
const storage = buildIndex(data, PIVOT);
assertStorageEntries(
storage, /* fieldNumber= */ 1,
Field.encodeIndexEntry(WireType.FIXED32, /* startIndex= */ 1));
}
});
it('fail for fixed32 tag that has no data after it', () => {
if (CHECK_CRITICAL_STATE) {
const data = createBufferDecoder(0x0D);
expect(() => buildIndex(data, PIVOT))
.toThrowError('Index out of bounds: index: 5 size: 1');
} else {
// Note in unchecked mode we produce invalid output for invalid inputs.
// This test just documents our behavior in those cases.
// These values might change at any point and are not considered
// what the implementation should be doing here.
const data = createBufferDecoder(0x0D);
const storage = buildIndex(data, PIVOT);
assertStorageEntries(
storage, /* fieldNumber= */ 1,
Field.encodeIndexEntry(WireType.FIXED32, /* startIndex= */ 1));
}
});
it('index group', () => {
const data = createBufferDecoder(
/* first= */ 0x0B, 0x08, 0x01, 0x0C, /* second= */ 0x0B, 0x08, 0x01,
0x0C);
const storage = buildIndex(data, PIVOT);
assertStorageEntries(
storage, /* fieldNumber= */ 1,
Field.encodeIndexEntry(WireType.START_GROUP, /* startIndex= */ 1),
Field.encodeIndexEntry(WireType.START_GROUP, /* startIndex= */ 5));
});
it('index group and skips inner group', () => {
const data =
createBufferDecoder(0x0B, 0x0B, 0x08, 0x01, 0x0C, 0x08, 0x01, 0x0C);
const storage = buildIndex(data, PIVOT);
assertStorageEntries(
storage, /* fieldNumber= */ 1,
Field.encodeIndexEntry(WireType.START_GROUP, /* startIndex= */ 1));
});
it('fail on unmatched stop group', () => {
const data = createBufferDecoder(0x0C, 0x01);
expect(() => buildIndex(data, PIVOT))
.toThrowError('Unexpected wire type: 4');
});
it('fail for groups without matching stop group', () => {
const data = createBufferDecoder(0x0B, 0x08, 0x01, 0x1C);
if (CHECK_CRITICAL_STATE) {
expect(() => buildIndex(data, PIVOT))
.toThrowError('Expected stop group for fieldnumber 1 not found.');
} else {
// Note in unchecked mode we produce invalid output for invalid inputs.
// This test just documents our behavior in those cases.
// These values might change at any point and are not considered
// what the implementation should be doing here.
const storage = buildIndex(data, PIVOT);
assertStorageEntries(
storage, /* fieldNumber= */ 1,
Field.encodeIndexEntry(WireType.START_GROUP, /* startIndex= */ 1));
}
});
it('fail for groups without stop group', () => {
const data = createBufferDecoder(0x0B, 0x08, 0x01);
if (CHECK_CRITICAL_STATE) {
expect(() => buildIndex(data, PIVOT)).toThrowError('No end group found.');
} else {
// Note in unchecked mode we produce invalid output for invalid inputs.
// This test just documents our behavior in those cases.
// These values might change at any point and are not considered
// what the implementation should be doing here.
const storage = buildIndex(data, PIVOT);
assertStorageEntries(
storage, /* fieldNumber= */ 1,
Field.encodeIndexEntry(WireType.START_GROUP, /* startIndex= */ 1));
}
});
it('fail for group tag that has no data after it', () => {
const data = createBufferDecoder(0x0B);
if (CHECK_CRITICAL_STATE) {
expect(() => buildIndex(data, PIVOT)).toThrowError('No end group found.');
} else {
// Note in unchecked mode we produce invalid output for invalid inputs.
// This test just documents our behavior in those cases.
// These values might change at any point and are not considered
// what the implementation should be doing here.
const storage = buildIndex(data, PIVOT);
assertStorageEntries(
storage, /* fieldNumber= */ 1,
Field.encodeIndexEntry(WireType.START_GROUP, /* startIndex= */ 1));
}
});
it('index too large tag', () => {
const data = createBufferDecoder(0xF8, 0xFF, 0xFF, 0xFF, 0xFF);
expect(() => buildIndex(data, PIVOT)).toThrow();
});
it('fail for varint tag that has no data after it', () => {
const data = createBufferDecoder(0x08);
expect(() => buildIndex(data, PIVOT)).toThrow();
});
});

View File

@ -0,0 +1,71 @@
/**
* @fileoverview Test data for int32 encoding and decoding.
*/
goog.module('protobuf.binary.int32TestPairs');
const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
/**
* An array of Pairs of float values and their bit representation.
* This is used to test encoding and decoding from/to the protobuf wire format.
* @return {!Array<{name: string, intValue:number, bufferDecoder:
* !BufferDecoder, error: ?boolean, skip_writer: ?boolean}>}
*/
function getInt32Pairs() {
const int32Pairs = [
{
name: 'zero',
intValue: 0,
bufferDecoder: createBufferDecoder(0x00),
},
{
name: 'one ',
intValue: 1,
bufferDecoder: createBufferDecoder(0x01),
},
{
name: 'minus one',
intValue: -1,
bufferDecoder: createBufferDecoder(0xFF, 0xFF, 0xFF, 0xFF, 0x0F),
// The writer will encode this with 64 bits, see below
skip_writer: true,
},
{
name: 'minus one (64bits)',
intValue: -1,
bufferDecoder: createBufferDecoder(
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01),
},
{
name: 'max signed int 2^31 - 1',
intValue: Math.pow(2, 31) - 1,
bufferDecoder: createBufferDecoder(0xFF, 0xFF, 0xFF, 0xFF, 0x07),
},
{
name: 'min signed int -2^31',
intValue: -Math.pow(2, 31),
bufferDecoder: createBufferDecoder(0x80, 0x80, 0x80, 0x80, 0x08),
// The writer will encode this with 64 bits, see below
skip_writer: true,
},
{
name: 'value min signed int -2^31 (64 bit)',
intValue: -Math.pow(2, 31),
bufferDecoder: createBufferDecoder(
0x80, 0x80, 0x80, 0x80, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0x01),
},
{
name: 'errors out for 11 bytes',
intValue: -1,
bufferDecoder: createBufferDecoder(
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF),
error: true,
skip_writer: true,
},
];
return [...int32Pairs];
}
exports = {getInt32Pairs};

View File

@ -0,0 +1,59 @@
/**
* @fileoverview Test data for int64 encoding and decoding.
*/
goog.module('protobuf.binary.int64TestPairs');
const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
const Int64 = goog.require('protobuf.Int64');
const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
/**
* An array of Pairs of float values and their bit representation.
* This is used to test encoding and decoding from/to the protobuf wire format.
* @return {!Array<{name: string, longValue: !Int64, bufferDecoder:
* !BufferDecoder, error: ?boolean, skip_writer: ?boolean}>}
*/
function getInt64Pairs() {
const int64Pairs = [
{
name: 'zero',
longValue: Int64.fromInt(0),
bufferDecoder: createBufferDecoder(0x00),
},
{
name: 'one ',
longValue: Int64.fromInt(1),
bufferDecoder: createBufferDecoder(0x01),
},
{
name: 'minus one',
longValue: Int64.fromInt(-1),
bufferDecoder: createBufferDecoder(
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01),
},
{
name: 'max signed int 2^63 - 1',
longValue: Int64.fromBits(0xFFFFFFFF, 0x7FFFFFFF),
bufferDecoder: createBufferDecoder(
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F),
},
{
name: 'value min signed int -2^63 (64 bit)',
longValue: Int64.fromBits(0xFFFFFFFF, 0xFFFFFFFF),
bufferDecoder: createBufferDecoder(
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01),
},
{
name: 'errors out for 11 bytes',
longValue: Int64.fromInt(-1),
bufferDecoder: createBufferDecoder(
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF),
error: true,
skip_writer: true,
},
];
return [...int64Pairs];
}
exports = {getInt64Pairs};

View File

@ -0,0 +1,24 @@
/**
* @fileoverview Internal interface for messages implemented with the binary
* kernel.
*/
goog.module('protobuf.binary.InternalMessage');
const Kernel = goog.requireType('protobuf.runtime.Kernel');
/**
* Interface that needs to be implemented by messages implemented with the
* binary kernel. This is an internal only interface and should be used only by
* the classes in binary kernel.
*
* @interface
*/
class InternalMessage {
/**
* @package
* @return {!Kernel}
*/
internalGetKernel() {}
}
exports = InternalMessage;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,266 @@
/**
* @fileoverview Tests to make sure Kernel can read data in a backward
* compatible way even when protobuf schema changes according to the rules
* defined in
* https://developers.google.com/protocol-buffers/docs/proto#updating and
* https://developers.google.com/protocol-buffers/docs/proto3#updating.
*
* third_party/protobuf/conformance/binary_json_conformance_suite.cc already
* covers many compatibility tests, this file covers only the tests not covered
* by binary_json_conformance_suite. Ultimately all of the tests in this file
* should be moved to binary_json_conformance_suite.
*/
goog.module('protobuf.runtime.KernelCompatibilityTest');
goog.setTestOnly();
const ByteString = goog.require('protobuf.ByteString');
const Int64 = goog.require('protobuf.Int64');
const Kernel = goog.require('protobuf.runtime.Kernel');
const TestMessage = goog.require('protobuf.testing.binary.TestMessage');
const {CHECK_CRITICAL_STATE} = goog.require('protobuf.internal.checks');
/**
* @param {...number} bytes
* @return {!ArrayBuffer}
*/
function createArrayBuffer(...bytes) {
return new Uint8Array(bytes).buffer;
}
/**
* Returns the Unicode character codes of a string.
* @param {string} str
* @return {!Array<number>}
*/
function getCharacterCodes(str) {
return Array.from(str, (c) => c.charCodeAt(0));
}
describe('optional -> repeated compatibility', () => {
it('is maintained for scalars', () => {
const oldAccessor = Kernel.createEmpty();
oldAccessor.setInt32(1, 1);
const serializedData = oldAccessor.serialize();
expect(serializedData).toEqual(createArrayBuffer(0x8, 0x1));
const newAccessor = Kernel.fromArrayBuffer(serializedData);
expect(newAccessor.getRepeatedInt32Size(1)).toEqual(1);
expect(newAccessor.getRepeatedInt32Element(1, 0)).toEqual(1);
});
it('is maintained for messages', () => {
const message = new TestMessage(Kernel.createEmpty());
message.setInt32(1, 1);
const oldAccessor = Kernel.createEmpty();
oldAccessor.setMessage(1, message);
const serializedData = oldAccessor.serialize();
expect(serializedData).toEqual(createArrayBuffer(0xA, 0x2, 0x8, 0x1));
const newAccessor = Kernel.fromArrayBuffer(serializedData);
expect(newAccessor.getRepeatedMessageSize(1, TestMessage.instanceCreator))
.toEqual(1);
expect(
newAccessor.getRepeatedMessageElement(1, TestMessage.instanceCreator, 0)
.serialize())
.toEqual(message.serialize());
});
it('is maintained for bytes', () => {
const message = new TestMessage(Kernel.createEmpty());
message.setInt32(1, 1);
const oldAccessor = Kernel.createEmpty();
oldAccessor.setBytes(
1, ByteString.fromArrayBuffer(createArrayBuffer(0xA, 0xB)));
const serializedData = oldAccessor.serialize();
expect(serializedData).toEqual(createArrayBuffer(0xA, 0x2, 0xA, 0xB));
const newAccessor = Kernel.fromArrayBuffer(serializedData);
expect(newAccessor.getRepeatedBytesSize(1)).toEqual(1);
expect(newAccessor.getRepeatedBoolElement(1, 0))
.toEqual(ByteString.fromArrayBuffer(createArrayBuffer(0xA, 0xB)));
});
it('is maintained for strings', () => {
const oldAccessor = Kernel.createEmpty();
oldAccessor.setString(1, 'hello');
const serializedData = oldAccessor.serialize();
expect(serializedData)
.toEqual(createArrayBuffer(0xA, 0x5, 0x68, 0x65, 0x6C, 0x6C, 0x6F));
const newAccessor = Kernel.fromArrayBuffer(serializedData);
expect(newAccessor.getRepeatedStringSize(1)).toEqual(1);
expect(newAccessor.getRepeatedStringElement(1, 0)).toEqual('hello');
});
});
describe('Kernel repeated -> optional compatibility', () => {
it('is maintained for unpacked scalars', () => {
const oldAccessor = Kernel.createEmpty();
oldAccessor.addUnpackedInt32Element(1, 0);
oldAccessor.addUnpackedInt32Element(1, 1);
const serializedData = oldAccessor.serialize();
expect(serializedData).toEqual(createArrayBuffer(0x8, 0x0, 0x8, 0x1));
const newAccessor = Kernel.fromArrayBuffer(serializedData);
expect(newAccessor.getInt32WithDefault(1)).toEqual(1);
expect(newAccessor.serialize()).toEqual(serializedData);
});
// repeated -> optional transformation is not supported for packed fields yet:
// go/proto-schema-repeated
it('is not maintained for packed scalars', () => {
const oldAccessor = Kernel.createEmpty();
oldAccessor.addPackedInt32Element(1, 0);
oldAccessor.addPackedInt32Element(1, 1);
const serializedData = oldAccessor.serialize();
expect(serializedData).toEqual(createArrayBuffer(0xA, 0x2, 0x0, 0x1));
const newAccessor = Kernel.fromArrayBuffer(serializedData);
if (CHECK_CRITICAL_STATE) {
expect(() => newAccessor.getInt32WithDefault(1)).toThrow();
}
});
it('is maintained for messages', () => {
const message1 = new TestMessage(Kernel.createEmpty());
message1.setInt32(1, 1);
const message2 = new TestMessage(Kernel.createEmpty());
message2.setInt32(1, 2);
message2.setInt32(2, 3);
const oldAccessor = Kernel.createEmpty();
oldAccessor.addRepeatedMessageElement(
1, message1, TestMessage.instanceCreator);
oldAccessor.addRepeatedMessageElement(
1, message2, TestMessage.instanceCreator);
const serializedData = oldAccessor.serialize();
expect(serializedData)
.toEqual(createArrayBuffer(
0xA, 0x2, 0x8, 0x1, 0xA, 0x4, 0x8, 0x2, 0x10, 0x3));
const newAccessor = Kernel.fromArrayBuffer(serializedData);
// Values from message1 and message2 have been merged
const newMessage = newAccessor.getMessage(1, TestMessage.instanceCreator);
expect(newMessage.getRepeatedInt32Size(1)).toEqual(2);
expect(newMessage.getRepeatedInt32Element(1, 0)).toEqual(1);
expect(newMessage.getRepeatedInt32Element(1, 1)).toEqual(2);
expect(newMessage.getInt32WithDefault(2)).toEqual(3);
expect(newMessage.serialize())
.toEqual(createArrayBuffer(0x8, 0x1, 0x8, 0x2, 0x10, 0x3));
});
it('is maintained for bytes', () => {
const oldAccessor = Kernel.createEmpty();
oldAccessor.addRepeatedBytesElement(
1, ByteString.fromArrayBuffer(createArrayBuffer(0xA, 0xB)));
oldAccessor.addRepeatedBytesElement(
1, ByteString.fromArrayBuffer(createArrayBuffer(0xC, 0xD)));
const serializedData = oldAccessor.serialize();
expect(serializedData)
.toEqual(createArrayBuffer(0xA, 0x2, 0xA, 0xB, 0xA, 0x2, 0xC, 0xD));
const newAccessor = Kernel.fromArrayBuffer(serializedData);
expect(newAccessor.getBytesWithDefault(1))
.toEqual(ByteString.fromArrayBuffer(createArrayBuffer(0xC, 0xD)));
expect(newAccessor.serialize()).toEqual(serializedData);
});
it('is maintained for strings', () => {
const oldAccessor = Kernel.createEmpty();
oldAccessor.addRepeatedStringElement(1, 'hello');
oldAccessor.addRepeatedStringElement(1, 'world');
const serializedData = oldAccessor.serialize();
expect(serializedData)
.toEqual(createArrayBuffer(
0xA, 0x5, ...getCharacterCodes('hello'), 0xA, 0x5,
...getCharacterCodes('world')));
const newAccessor = Kernel.fromArrayBuffer(serializedData);
expect(newAccessor.getStringWithDefault(1)).toEqual('world');
expect(newAccessor.serialize()).toEqual(serializedData);
});
});
describe('Type change', () => {
it('is supported for fixed32 -> sfixed32', () => {
const oldAccessor = Kernel.createEmpty();
oldAccessor.setFixed32(1, 4294967295);
const serializedData = oldAccessor.serialize();
expect(serializedData)
.toEqual(createArrayBuffer(0xD, 0xFF, 0xFF, 0xFF, 0xFF));
const newAccessor = Kernel.fromArrayBuffer(serializedData);
expect(newAccessor.getSfixed32WithDefault(1)).toEqual(-1);
expect(newAccessor.serialize()).toEqual(serializedData);
});
it('is supported for sfixed32 -> fixed32', () => {
const oldAccessor = Kernel.createEmpty();
oldAccessor.setSfixed32(1, -1);
const serializedData = oldAccessor.serialize();
expect(serializedData)
.toEqual(createArrayBuffer(0xD, 0xFF, 0xFF, 0xFF, 0xFF));
const newAccessor = Kernel.fromArrayBuffer(serializedData);
expect(newAccessor.getFixed32WithDefault(1)).toEqual(4294967295);
expect(newAccessor.serialize()).toEqual(serializedData);
});
it('is supported for fixed64 -> sfixed64', () => {
const oldAccessor = Kernel.createEmpty();
oldAccessor.setFixed64(1, Int64.fromHexString('0xFFFFFFFFFFFFFFFF'));
const serializedData = oldAccessor.serialize();
expect(serializedData)
.toEqual(createArrayBuffer(
0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF));
const newAccessor = Kernel.fromArrayBuffer(serializedData);
expect(newAccessor.getSfixed64WithDefault(1)).toEqual(Int64.fromInt(-1));
expect(newAccessor.serialize()).toEqual(serializedData);
});
it('is supported for sfixed64 -> fixed64', () => {
const oldAccessor = Kernel.createEmpty();
oldAccessor.setSfixed64(1, Int64.fromInt(-1));
const serializedData = oldAccessor.serialize();
expect(serializedData)
.toEqual(createArrayBuffer(
0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF));
const newAccessor = Kernel.fromArrayBuffer(serializedData);
expect(newAccessor.getFixed64WithDefault(1))
.toEqual(Int64.fromHexString('0xFFFFFFFFFFFFFFFF'));
expect(newAccessor.serialize()).toEqual(serializedData);
});
it('is supported for bytes -> message', () => {
const oldAccessor = Kernel.createEmpty();
oldAccessor.setBytes(
1, ByteString.fromArrayBuffer(createArrayBuffer(0x8, 0x1)));
const serializedData = oldAccessor.serialize();
expect(serializedData).toEqual(createArrayBuffer(0xA, 0x2, 0x8, 0x1));
const newAccessor = Kernel.fromArrayBuffer(serializedData);
const message = newAccessor.getMessage(1, TestMessage.instanceCreator);
expect(message.getInt32WithDefault(1)).toEqual(1);
expect(message.serialize()).toEqual(createArrayBuffer(0x8, 0x1));
expect(newAccessor.serialize()).toEqual(serializedData);
});
it('is supported for message -> bytes', () => {
const oldAccessor = Kernel.createEmpty();
const message = new TestMessage(Kernel.createEmpty());
message.setInt32(1, 1);
oldAccessor.setMessage(1, message);
const serializedData = oldAccessor.serialize();
expect(serializedData).toEqual(createArrayBuffer(0xA, 0x2, 0x8, 0x1));
const newAccessor = Kernel.fromArrayBuffer(serializedData);
expect(newAccessor.getBytesWithDefault(1))
.toEqual(ByteString.fromArrayBuffer(createArrayBuffer(0x8, 0x1)));
expect(newAccessor.serialize()).toEqual(serializedData);
});
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,285 @@
/*
##########################################################
# #
# __ __ _____ _ _ _____ _ _ _____ #
# \ \ / /\ | __ \| \ | |_ _| \ | |/ ____| #
# \ \ /\ / / \ | |__) | \| | | | | \| | | __ #
# \ \/ \/ / /\ \ | _ /| . ` | | | | . ` | | |_ | #
# \ /\ / ____ \| | \ \| |\ |_| |_| |\ | |__| | #
# \/ \/_/ \_\_| \_\_| \_|_____|_| \_|\_____| #
# #
# #
##########################################################
# Do not use this class in your code. This class purely #
# exists to make proto code generation easier. #
##########################################################
*/
goog.module('protobuf.runtime.MessageSet');
const InternalMessage = goog.require('protobuf.binary.InternalMessage');
const Kernel = goog.require('protobuf.runtime.Kernel');
// These are the tags for the old MessageSet format, which was defined as:
// message MessageSet {
// repeated group Item = 1 {
// required uint32 type_id = 2;
// optional bytes message = 3;
// }
// }
/** @const {number} */
const MSET_GROUP_FIELD_NUMBER = 1;
/** @const {number} */
const MSET_TYPE_ID_FIELD_NUMBER = 2;
/** @const {number} */
const MSET_MESSAGE_FIELD_NUMBER = 3;
/**
* @param {!Kernel} kernel
* @return {!Map<number, !Item>}
*/
function createItemMap(kernel) {
const itemMap = new Map();
let totalCount = 0;
for (const item of kernel.getRepeatedGroupIterable(
MSET_GROUP_FIELD_NUMBER, Item.fromKernel)) {
itemMap.set(item.getTypeId(), item);
totalCount++;
}
// Normalize the entries.
if (totalCount > itemMap.size) {
writeItemMap(kernel, itemMap);
}
return itemMap;
}
/**
* @param {!Kernel} kernel
* @param {!Map<number, !Item>} itemMap
*/
function writeItemMap(kernel, itemMap) {
kernel.setRepeatedGroupIterable(MSET_GROUP_FIELD_NUMBER, itemMap.values());
}
/**
* @implements {InternalMessage}
* @final
*/
class MessageSet {
/**
* @param {!Kernel} kernel
* @return {!MessageSet}
*/
static fromKernel(kernel) {
const itemMap = createItemMap(kernel);
return new MessageSet(kernel, itemMap);
}
/**
* @return {!MessageSet}
*/
static createEmpty() {
return MessageSet.fromKernel(Kernel.createEmpty());
}
/**
* @param {!Kernel} kernel
* @param {!Map<number, !Item>} itemMap
* @private
*/
constructor(kernel, itemMap) {
/** @const {!Kernel} @private */
this.kernel_ = kernel;
/** @const {!Map<number, !Item>} @private */
this.itemMap_ = itemMap;
}
// code helpers for code gen
/**
* @param {number} typeId
* @param {function(!Kernel):T} instanceCreator
* @param {number=} pivot
* @return {?T}
* @template T
*/
getMessageOrNull(typeId, instanceCreator, pivot) {
const item = this.itemMap_.get(typeId);
return item ? item.getMessageOrNull(instanceCreator, pivot) : null;
}
/**
* @param {number} typeId
* @param {function(!Kernel):T} instanceCreator
* @param {number=} pivot
* @return {T}
* @template T
*/
getMessageAttach(typeId, instanceCreator, pivot) {
let item = this.itemMap_.get(typeId);
if (item) {
return item.getMessageAttach(instanceCreator, pivot);
}
const message = instanceCreator(Kernel.createEmpty());
this.setMessage(typeId, message);
return message;
}
/**
* @param {number} typeId
* @param {number=} pivot
* @return {?Kernel}
*/
getMessageAccessorOrNull(typeId, pivot) {
const item = this.itemMap_.get(typeId);
return item ? item.getMessageAccessorOrNull(pivot) : null;
}
/**
* @param {number} typeId
*/
clearMessage(typeId) {
if (this.itemMap_.delete(typeId)) {
writeItemMap(this.kernel_, this.itemMap_);
}
}
/**
* @param {number} typeId
* @return {boolean}
*/
hasMessage(typeId) {
return this.itemMap_.has(typeId);
}
/**
* @param {number} typeId
* @param {!InternalMessage} value
*/
setMessage(typeId, value) {
const item = this.itemMap_.get(typeId);
if (item) {
item.setMessage(value);
} else {
this.itemMap_.set(typeId, Item.create(typeId, value));
writeItemMap(this.kernel_, this.itemMap_);
}
}
/**
* @return {!Kernel}
* @override
*/
internalGetKernel() {
return this.kernel_;
}
}
/**
* @implements {InternalMessage}
* @final
*/
class Item {
/**
* @param {number} typeId
* @param {!InternalMessage} message
* @return {!Item}
*/
static create(typeId, message) {
const messageSet = Item.fromKernel(Kernel.createEmpty());
messageSet.setTypeId_(typeId);
messageSet.setMessage(message);
return messageSet;
}
/**
* @param {!Kernel} kernel
* @return {!Item}
*/
static fromKernel(kernel) {
return new Item(kernel);
}
/**
* @param {!Kernel} kernel
* @private
*/
constructor(kernel) {
/** @const {!Kernel} @private */
this.kernel_ = kernel;
}
/**
* @param {function(!Kernel):T} instanceCreator
* @param {number=} pivot
* @return {T}
* @template T
*/
getMessage(instanceCreator, pivot) {
return this.kernel_.getMessage(
MSET_MESSAGE_FIELD_NUMBER, instanceCreator, pivot);
}
/**
* @param {function(!Kernel):T} instanceCreator
* @param {number=} pivot
* @return {?T}
* @template T
*/
getMessageOrNull(instanceCreator, pivot) {
return this.kernel_.getMessageOrNull(
MSET_MESSAGE_FIELD_NUMBER, instanceCreator, pivot);
}
/**
* @param {function(!Kernel):T} instanceCreator
* @param {number=} pivot
* @return {T}
* @template T
*/
getMessageAttach(instanceCreator, pivot) {
return this.kernel_.getMessageAttach(
MSET_MESSAGE_FIELD_NUMBER, instanceCreator, pivot);
}
/**
* @param {number=} pivot
* @return {?Kernel}
*/
getMessageAccessorOrNull(pivot) {
return this.kernel_.getMessageAccessorOrNull(
MSET_MESSAGE_FIELD_NUMBER, pivot);
}
/** @param {!InternalMessage} value */
setMessage(value) {
this.kernel_.setMessage(MSET_MESSAGE_FIELD_NUMBER, value);
}
/** @return {number} */
getTypeId() {
return this.kernel_.getUint32WithDefault(MSET_TYPE_ID_FIELD_NUMBER);
}
/**
* @param {number} value
* @private
*/
setTypeId_(value) {
this.kernel_.setUint32(MSET_TYPE_ID_FIELD_NUMBER, value);
}
/**
* @return {!Kernel}
* @override
*/
internalGetKernel() {
return this.kernel_;
}
}
exports = MessageSet;

View File

@ -0,0 +1,262 @@
/**
* @fileoverview Tests for message_set.js.
*/
goog.module('protobuf.runtime.MessageSetTest');
goog.setTestOnly();
const Kernel = goog.require('protobuf.runtime.Kernel');
const MessageSet = goog.require('protobuf.runtime.MessageSet');
const TestMessage = goog.require('protobuf.testing.binary.TestMessage');
/**
* @param {...number} bytes
* @return {!ArrayBuffer}
*/
function createArrayBuffer(...bytes) {
return new Uint8Array(bytes).buffer;
}
describe('MessageSet does', () => {
it('returns no messages for empty set', () => {
const messageSet = MessageSet.createEmpty();
expect(messageSet.getMessageOrNull(12345, TestMessage.instanceCreator))
.toBeNull();
});
it('returns no kernel for empty set', () => {
const messageSet = MessageSet.createEmpty();
expect(messageSet.getMessageAccessorOrNull(12345)).toBeNull();
});
it('returns message that has been set', () => {
const messageSet = MessageSet.createEmpty();
const message = TestMessage.createEmpty();
messageSet.setMessage(12345, message);
expect(messageSet.getMessageOrNull(12345, TestMessage.instanceCreator))
.toBe(message);
});
it('returns null for cleared message', () => {
const messageSet = MessageSet.createEmpty();
const message = TestMessage.createEmpty();
messageSet.setMessage(12345, message);
messageSet.clearMessage(12345);
expect(messageSet.getMessageAccessorOrNull(12345)).toBeNull();
});
it('returns false for not present message', () => {
const messageSet = MessageSet.createEmpty();
expect(messageSet.hasMessage(12345)).toBe(false);
});
it('returns true for present message', () => {
const messageSet = MessageSet.createEmpty();
const message = TestMessage.createEmpty();
messageSet.setMessage(12345, message);
expect(messageSet.hasMessage(12345)).toBe(true);
});
it('returns false for cleared message', () => {
const messageSet = MessageSet.createEmpty();
const message = TestMessage.createEmpty();
messageSet.setMessage(12345, message);
messageSet.clearMessage(12345);
expect(messageSet.hasMessage(12345)).toBe(false);
});
it('returns false for cleared message without it being present', () => {
const messageSet = MessageSet.createEmpty();
messageSet.clearMessage(12345);
expect(messageSet.hasMessage(12345)).toBe(false);
});
const createMessageSet = () => {
const messageSet = MessageSet.createEmpty();
const message = TestMessage.createEmpty();
message.setInt32(1, 2);
messageSet.setMessage(12345, message);
const parsedKernel =
Kernel.fromArrayBuffer(messageSet.internalGetKernel().serialize());
return MessageSet.fromKernel(parsedKernel);
};
it('pass through pivot for getMessageOrNull', () => {
const messageSet = createMessageSet();
const message =
messageSet.getMessageOrNull(12345, TestMessage.instanceCreator, 2);
expect(message.internalGetKernel().getPivot()).toBe(2);
});
it('pass through pivot for getMessageAttach', () => {
const messageSet = createMessageSet();
const message =
messageSet.getMessageAttach(12345, TestMessage.instanceCreator, 2);
expect(message.internalGetKernel().getPivot()).toBe(2);
});
it('pass through pivot for getMessageAccessorOrNull', () => {
const messageSet = createMessageSet();
const kernel = messageSet.getMessageAccessorOrNull(12345, 2);
expect(kernel.getPivot()).toBe(2);
});
it('pick the last value in the stream', () => {
const arrayBuffer = createArrayBuffer(
0x52, // Tag (field:10, length delimited)
0x14, // Length of 20 bytes
0x0B, // Start group fieldnumber 1
0x10, // Tag (field 2, varint)
0xB9, // 12345
0x60, // 12345
0x1A, // Tag (field 3, length delimited)
0x03, // length 3
0xA0, // Tag (fieldnumber 20, varint)
0x01, // Tag (fieldnumber 20, varint)
0x1E, // 30
0x0C, // Stop Group field number 1
// second group
0x0B, // Start group fieldnumber 1
0x10, // Tag (field 2, varint)
0xB9, // 12345
0x60, // 12345
0x1A, // Tag (field 3, length delimited)
0x03, // length 3
0xA0, // Tag (fieldnumber 20, varint)
0x01, // Tag (fieldnumber 20, varint)
0x01, // 1
0x0C // Stop Group field number 1
);
const outerMessage = Kernel.fromArrayBuffer(arrayBuffer);
const messageSet = outerMessage.getMessage(10, MessageSet.fromKernel);
const message =
messageSet.getMessageOrNull(12345, TestMessage.instanceCreator);
expect(message.getInt32WithDefault(20)).toBe(1);
});
it('removes duplicates when read', () => {
const arrayBuffer = createArrayBuffer(
0x52, // Tag (field:10, length delimited)
0x14, // Length of 20 bytes
0x0B, // Start group fieldnumber 1
0x10, // Tag (field 2, varint)
0xB9, // 12345
0x60, // 12345
0x1A, // Tag (field 3, length delimited)
0x03, // length 3
0xA0, // Tag (fieldnumber 20, varint)
0x01, // Tag (fieldnumber 20, varint)
0x1E, // 30
0x0C, // Stop Group field number 1
// second group
0x0B, // Start group fieldnumber 1
0x10, // Tag (field 2, varint)
0xB9, // 12345
0x60, // 12345
0x1A, // Tag (field 3, length delimited)
0x03, // length 3
0xA0, // Tag (fieldnumber 20, varint)
0x01, // Tag (fieldnumber 20, varint)
0x01, // 1
0x0C // Stop Group field number 1
);
const outerMessage = Kernel.fromArrayBuffer(arrayBuffer);
outerMessage.getMessageAttach(10, MessageSet.fromKernel);
expect(outerMessage.serialize())
.toEqual(createArrayBuffer(
0x52, // Tag (field:10, length delimited)
0x0A, // Length of 10 bytes
0x0B, // Start group fieldnumber 1
0x10, // Tag (field 2, varint)
0xB9, // 12345
0x60, // 12345
0x1A, // Tag (field 3, length delimited)
0x03, // length 3
0xA0, // Tag (fieldnumber 20, varint)
0x01, // Tag (fieldnumber 20, varint)
0x01, // 1
0x0C // Stop Group field number 1
));
});
it('allow for large typeIds', () => {
const messageSet = MessageSet.createEmpty();
const message = TestMessage.createEmpty();
messageSet.setMessage(0xFFFFFFFE >>> 0, message);
expect(messageSet.hasMessage(0xFFFFFFFE >>> 0)).toBe(true);
});
});
describe('Optional MessageSet does', () => {
// message Bar {
// optional MessageSet mset = 10;
//}
//
// message Foo {
// extend proto2.bridge.MessageSet {
// optional Foo message_set_extension = 12345;
// }
// optional int32 f20 = 20;
//}
it('encode as a field', () => {
const fooMessage = Kernel.createEmpty();
fooMessage.setInt32(20, 30);
const messageSet = MessageSet.createEmpty();
messageSet.setMessage(12345, TestMessage.instanceCreator(fooMessage));
const barMessage = Kernel.createEmpty();
barMessage.setMessage(10, messageSet);
expect(barMessage.serialize())
.toEqual(createArrayBuffer(
0x52, // Tag (field:10, length delimited)
0x0A, // Length of 10 bytes
0x0B, // Start group fieldnumber 1
0x10, // Tag (field 2, varint)
0xB9, // 12345
0x60, // 12345
0x1A, // Tag (field 3, length delimited)
0x03, // length 3
0xA0, // Tag (fieldnumber 20, varint)
0x01, // Tag (fieldnumber 20, varint)
0x1E, // 30
0x0C // Stop Group field number 1
));
});
it('deserializes', () => {
const fooMessage = Kernel.createEmpty();
fooMessage.setInt32(20, 30);
const messageSet = MessageSet.createEmpty();
messageSet.setMessage(12345, TestMessage.instanceCreator(fooMessage));
const barMessage = Kernel.createEmpty();
barMessage.setMessage(10, messageSet);
const arrayBuffer = barMessage.serialize();
const barMessageParsed = Kernel.fromArrayBuffer(arrayBuffer);
expect(barMessageParsed.hasFieldNumber(10)).toBe(true);
const messageSetParsed =
barMessageParsed.getMessage(10, MessageSet.fromKernel);
const fooMessageParsed =
messageSetParsed.getMessageOrNull(12345, TestMessage.instanceCreator)
.internalGetKernel();
expect(fooMessageParsed.getInt32WithDefault(20)).toBe(30);
});
});

View File

@ -0,0 +1,59 @@
goog.module('protobuf.binary.packedBoolTestPairs');
const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
/**
* An array of Pairs of packed bool values and their bit representation.
* This is used to test encoding and decoding from/to the protobuf wire format.
* @return {!Array<{name: string, boolValues: !Array<boolean>,
* bufferDecoder: !BufferDecoder, skip_writer: ?boolean}>}
*/
function getPackedBoolPairs() {
return [
{
name: 'empty value',
boolValues: [],
bufferDecoder: createBufferDecoder(0x00),
skip_writer: true,
},
{
name: 'single value',
boolValues: [true],
bufferDecoder: createBufferDecoder(0x01, 0x01),
},
{
name: 'single multi-bytes value',
boolValues: [true],
bufferDecoder: createBufferDecoder(0x02, 0x80, 0x01),
skip_writer: true,
},
{
name: 'multiple values',
boolValues: [true, false],
bufferDecoder: createBufferDecoder(0x02, 0x01, 0x00),
},
{
name: 'multiple multi-bytes values',
boolValues: [true, false],
bufferDecoder: createBufferDecoder(
0x0C, // length
0x80,
0x80,
0x80,
0x80,
0x80,
0x01, // true
0x80,
0x80,
0x80,
0x80,
0x80,
0x00, // false
),
skip_writer: true,
},
];
}
exports = {getPackedBoolPairs};

View File

@ -0,0 +1,52 @@
goog.module('protobuf.binary.packedDoubleTestPairs');
const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
/**
* An array of Pairs of packed double values and their bit representation.
* This is used to test encoding and decoding from/to the protobuf wire format.
* @return {!Array<{name: string, doubleValues: !Array<number>,
* bufferDecoder: !BufferDecoder, skip_writer: ?boolean}>}
*/
function getPackedDoublePairs() {
return [
{
name: 'empty value',
doubleValues: [],
bufferDecoder: createBufferDecoder(0x00),
skip_writer: true,
},
{
name: 'single value',
doubleValues: [1],
bufferDecoder: createBufferDecoder(
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F),
},
{
name: 'multiple values',
doubleValues: [1, 0],
bufferDecoder: createBufferDecoder(
0x10, // length
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0xF0,
0x3F, // 1
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00, // 0
),
},
];
}
exports = {getPackedDoublePairs};

View File

@ -0,0 +1,34 @@
goog.module('protobuf.binary.packedFixed32TestPairs');
const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
/**
* An array of Pairs of packed fixed32 values and their bit representation.
* This is used to test encoding and decoding from/to the protobuf wire format.
* @return {!Array<{name: string, fixed32Values: !Array<number>,
* bufferDecoder: !BufferDecoder, skip_writer: ?boolean}>}
*/
function getPackedFixed32Pairs() {
return [
{
name: 'empty value',
fixed32Values: [],
bufferDecoder: createBufferDecoder(0x00),
skip_writer: true,
},
{
name: 'single value',
fixed32Values: [1],
bufferDecoder: createBufferDecoder(0x04, 0x01, 0x00, 0x00, 0x00),
},
{
name: 'multiple values',
fixed32Values: [1, 0],
bufferDecoder: createBufferDecoder(
0x08, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
},
];
}
exports = {getPackedFixed32Pairs};

View File

@ -0,0 +1,34 @@
goog.module('protobuf.binary.packedFloatTestPairs');
const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
/**
* An array of Pairs of packed float values and their bit representation.
* This is used to test encoding and decoding from/to the protobuf wire format.
* @return {!Array<{name: string, floatValues: !Array<number>,
* bufferDecoder: !BufferDecoder, skip_writer: ?boolean}>}
*/
function getPackedFloatPairs() {
return [
{
name: 'empty value',
floatValues: [],
bufferDecoder: createBufferDecoder(0x00),
skip_writer: true,
},
{
name: 'single value',
floatValues: [1],
bufferDecoder: createBufferDecoder(0x04, 0x00, 0x00, 0x80, 0x3F),
},
{
name: 'multiple values',
floatValues: [1, 0],
bufferDecoder: createBufferDecoder(
0x08, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00),
},
];
}
exports = {getPackedFloatPairs};

View File

@ -0,0 +1,33 @@
goog.module('protobuf.binary.packedInt32TestPairs');
const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
/**
* An array of Pairs of packed int32 values and their bit representation.
* This is used to test encoding and decoding from/to the protobuf wire format.
* @return {!Array<{name: string, int32Values: !Array<number>,
* bufferDecoder: !BufferDecoder, skip_writer: ?boolean}>}
*/
function getPackedInt32Pairs() {
return [
{
name: 'empty value',
int32Values: [],
bufferDecoder: createBufferDecoder(0x00),
skip_writer: true,
},
{
name: 'single value',
int32Values: [1],
bufferDecoder: createBufferDecoder(0x01, 0x01),
},
{
name: 'multiple values',
int32Values: [1, 0],
bufferDecoder: createBufferDecoder(0x02, 0x01, 0x00),
},
];
}
exports = {getPackedInt32Pairs};

View File

@ -0,0 +1,34 @@
goog.module('protobuf.binary.packedInt64TestPairs');
const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
const Int64 = goog.require('protobuf.Int64');
const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
/**
* An array of Pairs of packed int64 values and their bit representation.
* This is used to test encoding and decoding from/to the protobuf wire format.
* @return {!Array<{name: string, int64Values: !Array<!Int64>,
* bufferDecoder: !BufferDecoder, skip_writer: ?boolean}>}
*/
function getPackedInt64Pairs() {
return [
{
name: 'empty value',
int64Values: [],
bufferDecoder: createBufferDecoder(0x00),
skip_writer: true,
},
{
name: 'single value',
int64Values: [Int64.fromInt(1)],
bufferDecoder: createBufferDecoder(0x01, 0x01),
},
{
name: 'multiple values',
int64Values: [Int64.fromInt(1), Int64.fromInt(0)],
bufferDecoder: createBufferDecoder(0x02, 0x01, 0x00),
},
];
}
exports = {getPackedInt64Pairs};

View File

@ -0,0 +1,34 @@
goog.module('protobuf.binary.packedSfixed32TestPairs');
const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
/**
* An array of Pairs of packed sfixed32 values and their bit representation.
* This is used to test encoding and decoding from/to the protobuf wire format.
* @return {!Array<{name: string, sfixed32Values: !Array<number>,
* bufferDecoder: !BufferDecoder, skip_writer: ?boolean}>}
*/
function getPackedSfixed32Pairs() {
return [
{
name: 'empty value',
sfixed32Values: [],
bufferDecoder: createBufferDecoder(0x00),
skip_writer: true,
},
{
name: 'single value',
sfixed32Values: [1],
bufferDecoder: createBufferDecoder(0x04, 0x01, 0x00, 0x00, 0x00),
},
{
name: 'multiple values',
sfixed32Values: [1, 0],
bufferDecoder: createBufferDecoder(
0x08, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
},
];
}
exports = {getPackedSfixed32Pairs};

View File

@ -0,0 +1,53 @@
goog.module('protobuf.binary.packedSfixed64TestPairs');
const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
const Int64 = goog.require('protobuf.Int64');
const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
/**
* An array of Pairs of packed sfixed64 values and their bit representation.
* This is used to test encoding and decoding from/to the protobuf wire format.
* @return {!Array<{name: string, sfixed64Values: !Array<!Int64>,
* bufferDecoder: !BufferDecoder, skip_writer: ?boolean}>}
*/
function getPackedSfixed64Pairs() {
return [
{
name: 'empty value',
sfixed64Values: [],
bufferDecoder: createBufferDecoder(0x00),
skip_writer: true,
},
{
name: 'single value',
sfixed64Values: [Int64.fromInt(1)],
bufferDecoder: createBufferDecoder(
0x08, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
},
{
name: 'multiple values',
sfixed64Values: [Int64.fromInt(1), Int64.fromInt(0)],
bufferDecoder: createBufferDecoder(
0x10, // length
0x01,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00, // 1
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00, // 2
),
},
];
}
exports = {getPackedSfixed64Pairs};

View File

@ -0,0 +1,33 @@
goog.module('protobuf.binary.packedSint32TestPairs');
const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
/**
* An array of Pairs of packed sint32 values and their bit representation.
* This is used to test encoding and decoding from/to the protobuf wire format.
* @return {!Array<{name: string, sint32Values: !Array<number>,
* bufferDecoder: !BufferDecoder, skip_writer: ?boolean}>}
*/
function getPackedSint32Pairs() {
return [
{
name: 'empty value',
sint32Values: [],
bufferDecoder: createBufferDecoder(0x00),
skip_writer: true,
},
{
name: 'single value',
sint32Values: [-1],
bufferDecoder: createBufferDecoder(0x01, 0x01),
},
{
name: 'multiple values',
sint32Values: [-1, 0],
bufferDecoder: createBufferDecoder(0x02, 0x01, 0x00),
},
];
}
exports = {getPackedSint32Pairs};

View File

@ -0,0 +1,34 @@
goog.module('protobuf.binary.packedSint64TestPairs');
const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
const Int64 = goog.require('protobuf.Int64');
const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
/**
* An array of Pairs of packed sint64 values and their bit representation.
* This is used to test encoding and decoding from/to the protobuf wire format.
* @return {!Array<{name: string, sint64Values: !Array<number>,
* bufferDecoder: !BufferDecoder, skip_writer: ?boolean}>}
*/
function getPackedSint64Pairs() {
return [
{
name: 'empty value',
sint64Values: [],
bufferDecoder: createBufferDecoder(0x00),
skip_writer: true,
},
{
name: 'single value',
sint64Values: [Int64.fromInt(-1)],
bufferDecoder: createBufferDecoder(0x01, 0x01),
},
{
name: 'multiple values',
sint64Values: [Int64.fromInt(-1), Int64.fromInt(0)],
bufferDecoder: createBufferDecoder(0x02, 0x01, 0x00),
},
];
}
exports = {getPackedSint64Pairs};

View File

@ -0,0 +1,33 @@
goog.module('protobuf.binary.packedUint32TestPairs');
const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
/**
* An array of Pairs of packed uint32 values and their bit representation.
* This is used to test encoding and decoding from/to the protobuf wire format.
* @return {!Array<{name: string, uint32Values: !Array<number>,
* bufferDecoder: !BufferDecoder, skip_writer: ?boolean}>}
*/
function getPackedUint32Pairs() {
return [
{
name: 'empty value',
uint32Values: [],
bufferDecoder: createBufferDecoder(0x00),
skip_writer: true,
},
{
name: 'single value',
uint32Values: [1],
bufferDecoder: createBufferDecoder(0x01, 0x01),
},
{
name: 'multiple values',
uint32Values: [1, 0],
bufferDecoder: createBufferDecoder(0x02, 0x01, 0x00),
},
];
}
exports = {getPackedUint32Pairs};

View File

@ -0,0 +1,364 @@
/**
* @fileoverview Helper methods for reading data from the binary wire format.
*/
goog.module('protobuf.binary.reader');
const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
const ByteString = goog.require('protobuf.ByteString');
const Int64 = goog.require('protobuf.Int64');
const {checkState} = goog.require('protobuf.internal.checks');
/******************************************************************************
* OPTIONAL FUNCTIONS
******************************************************************************/
/**
* Reads a boolean value from the binary bytes.
* @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
* @param {number} start Start of the data.
* @return {boolean}
* @package
*/
function readBool(bufferDecoder, start) {
const {lowBits, highBits} = bufferDecoder.getVarint(start);
return lowBits !== 0 || highBits !== 0;
}
/**
* Reads a ByteString value from the binary bytes.
* @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
* @param {number} start Start of the data.
* @return {!ByteString}
* @package
*/
function readBytes(bufferDecoder, start) {
return readDelimited(bufferDecoder, start).asByteString();
}
/**
* Reads a int32 value from the binary bytes encoded as varint.
* @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
* @param {number} start Start of the data.
* @return {number}
* @package
*/
function readInt32(bufferDecoder, start) {
// Negative 32 bit integers are encoded with 64 bit values.
// Clients are expected to truncate back to 32 bits.
// This is why we are dropping the upper bytes here.
return bufferDecoder.getUnsignedVarint32At(start) | 0;
}
/**
* Reads a int32 value from the binary bytes encoded as varint.
* @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
* @param {number} start Start of the data.
* @return {!Int64}
* @package
*/
function readInt64(bufferDecoder, start) {
const {lowBits, highBits} = bufferDecoder.getVarint(start);
return Int64.fromBits(lowBits, highBits);
}
/**
* Reads a fixed int32 value from the binary bytes.
* @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
* @param {number} start Start of the data.
* @return {number}
* @package
*/
function readFixed32(bufferDecoder, start) {
return bufferDecoder.getUint32(start);
}
/**
* Reads a float value from the binary bytes.
* @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
* @param {number} start Start of the data.
* @return {number}
* @package
*/
function readFloat(bufferDecoder, start) {
return bufferDecoder.getFloat32(start);
}
/**
* Reads a fixed int64 value from the binary bytes.
* @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
* @param {number} start Start of the data.
* @return {!Int64}
* @package
*/
function readSfixed64(bufferDecoder, start) {
const lowBits = bufferDecoder.getInt32(start);
const highBits = bufferDecoder.getInt32(start + 4);
return Int64.fromBits(lowBits, highBits);
}
/**
* Reads a sint64 value from the binary bytes encoded as varint.
* @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
* @param {number} start Start of the data.
* @return {!Int64}
* @package
*/
function readSint64(bufferDecoder, start) {
const {lowBits, highBits} = bufferDecoder.getVarint(start);
const sign = -(lowBits & 0x01);
const decodedLowerBits = ((lowBits >>> 1) | (highBits & 0x01) << 31) ^ sign;
const decodedUpperBits = (highBits >>> 1) ^ sign;
return Int64.fromBits(decodedLowerBits, decodedUpperBits);
}
/**
* Reads a sint32 value from the binary bytes encoded as varint.
* @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
* @param {number} start Start of the data.
* @return {number}
* @package
*/
function readSint32(bufferDecoder, start) {
return readSint64(bufferDecoder, start).getLowBits();
}
/**
* Read a subarray of bytes representing a length delimited field.
* @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
* @param {number} start Start of the data.
* @return {!BufferDecoder}
* @package
*/
function readDelimited(bufferDecoder, start) {
const unsignedLength = bufferDecoder.getUnsignedVarint32At(start);
return bufferDecoder.subBufferDecoder(bufferDecoder.cursor(), unsignedLength);
}
/**
* Reads a string value from the binary bytes.
* @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
* @param {number} start Start of the data.
* @return {string}
* @package
*/
function readString(bufferDecoder, start) {
return readDelimited(bufferDecoder, start).asString();
}
/**
* Reads a uint32 value from the binary bytes encoded as varint.
* @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
* @param {number} start Start of the data.
* @return {number}
* @package
*/
function readUint32(bufferDecoder, start) {
return bufferDecoder.getUnsignedVarint32At(start);
}
/**
* Reads a double value from the binary bytes.
* @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
* @param {number} start Start of the data.
* @return {number}
* @package
*/
function readDouble(bufferDecoder, start) {
return bufferDecoder.getFloat64(start);
}
/**
* Reads a fixed int32 value from the binary bytes.
* @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
* @param {number} start Start of the data.
* @return {number}
* @package
*/
function readSfixed32(bufferDecoder, start) {
return bufferDecoder.getInt32(start);
}
/******************************************************************************
* REPEATED FUNCTIONS
******************************************************************************/
/**
* Reads a packed bool field, which consists of a length header and a list of
* unsigned varints.
* @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
* @param {number} start Start of the data.
* @return {!Array<boolean>}
* @package
*/
function readPackedBool(bufferDecoder, start) {
return readPacked(bufferDecoder, start, readBool);
}
/**
* Reads a packed double field, which consists of a length header and a list of
* fixed64.
* @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
* @param {number} start Start of the data.
* @return {!Array<number>}
* @package
*/
function readPackedDouble(bufferDecoder, start) {
return readPacked(bufferDecoder, start, readDouble);
}
/**
* Reads a packed fixed32 field, which consists of a length header and a list of
* fixed32.
* @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
* @param {number} start Start of the data.
* @return {!Array<number>}
* @package
*/
function readPackedFixed32(bufferDecoder, start) {
return readPacked(bufferDecoder, start, readFixed32);
}
/**
* Reads a packed float field, which consists of a length header and a list of
* fixed64.
* @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
* @param {number} start Start of the data.
* @return {!Array<number>}
* @package
*/
function readPackedFloat(bufferDecoder, start) {
return readPacked(bufferDecoder, start, readFloat);
}
/**
* Reads a packed int32 field, which consists of a length header and a list of
* varint.
* @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
* @param {number} start Start of the data.
* @return {!Array<number>}
* @package
*/
function readPackedInt32(bufferDecoder, start) {
return readPacked(bufferDecoder, start, readInt32);
}
/**
* Reads a packed int64 field, which consists of a length header and a list
* of int64.
* @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
* @param {number} start Start of the data.
* @return {!Array<!Int64>}
* @package
*/
function readPackedInt64(bufferDecoder, start) {
return readPacked(bufferDecoder, start, readInt64);
}
/**
* Reads a packed sfixed32 field, which consists of a length header and a list
* of sfixed32.
* @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
* @param {number} start Start of the data.
* @return {!Array<number>}
* @package
*/
function readPackedSfixed32(bufferDecoder, start) {
return readPacked(bufferDecoder, start, readSfixed32);
}
/**
* Reads a packed sfixed64 field, which consists of a length header and a list
* of sfixed64.
* @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
* @param {number} start Start of the data.
* @return {!Array<!Int64>}
* @package
*/
function readPackedSfixed64(bufferDecoder, start) {
return readPacked(bufferDecoder, start, readSfixed64);
}
/**
* Reads a packed sint32 field, which consists of a length header and a list of
* varint.
* @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
* @param {number} start Start of the data.
* @return {!Array<number>}
* @package
*/
function readPackedSint32(bufferDecoder, start) {
return readPacked(bufferDecoder, start, readSint32);
}
/**
* Reads a packed sint64 field, which consists of a length header and a list
* of sint64.
* @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
* @param {number} start Start of the data.
* @return {!Array<!Int64>}
* @package
*/
function readPackedSint64(bufferDecoder, start) {
return readPacked(bufferDecoder, start, readSint64);
}
/**
* Reads a packed uint32 field, which consists of a length header and a list of
* varint.
* @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
* @param {number} start Start of the data.
* @return {!Array<number>}
* @package
*/
function readPackedUint32(bufferDecoder, start) {
return readPacked(bufferDecoder, start, readUint32);
}
/**
* Read packed values.
* @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
* @param {number} start Start of the data.
* @param {function(!BufferDecoder, number):T} valueFunction
* @return {!Array<T>}
* @package
* @template T
*/
function readPacked(bufferDecoder, start, valueFunction) {
const /** !Array<T> */ result = [];
const unsignedLength = bufferDecoder.getUnsignedVarint32At(start);
const dataStart = bufferDecoder.cursor();
while (bufferDecoder.cursor() < dataStart + unsignedLength) {
checkState(bufferDecoder.cursor() > 0);
result.push(valueFunction(bufferDecoder, bufferDecoder.cursor()));
}
return result;
}
exports = {
readBool,
readBytes,
readDelimited,
readDouble,
readFixed32,
readFloat,
readInt32,
readInt64,
readSint32,
readSint64,
readSfixed32,
readSfixed64,
readString,
readUint32,
readPackedBool,
readPackedDouble,
readPackedFixed32,
readPackedFloat,
readPackedInt32,
readPackedInt64,
readPackedSfixed32,
readPackedSfixed64,
readPackedSint32,
readPackedSint64,
readPackedUint32,
};

View File

@ -0,0 +1,425 @@
/**
* @fileoverview Tests for reader.js.
*/
goog.module('protobuf.binary.ReaderTest');
goog.setTestOnly();
// Note to the reader:
// Since the reader behavior changes with the checking level some of the
// tests in this file have to know which checking level is enable to make
// correct assertions.
const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
const ByteString = goog.require('protobuf.ByteString');
const reader = goog.require('protobuf.binary.reader');
const {CHECK_CRITICAL_STATE} = goog.require('protobuf.internal.checks');
const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
const {encode} = goog.require('protobuf.binary.textencoding');
const {getBoolPairs} = goog.require('protobuf.binary.boolTestPairs');
const {getDoublePairs} = goog.require('protobuf.binary.doubleTestPairs');
const {getFixed32Pairs} = goog.require('protobuf.binary.fixed32TestPairs');
const {getFloatPairs} = goog.require('protobuf.binary.floatTestPairs');
const {getInt32Pairs} = goog.require('protobuf.binary.int32TestPairs');
const {getInt64Pairs} = goog.require('protobuf.binary.int64TestPairs');
const {getPackedBoolPairs} = goog.require('protobuf.binary.packedBoolTestPairs');
const {getPackedDoublePairs} = goog.require('protobuf.binary.packedDoubleTestPairs');
const {getPackedFixed32Pairs} = goog.require('protobuf.binary.packedFixed32TestPairs');
const {getPackedFloatPairs} = goog.require('protobuf.binary.packedFloatTestPairs');
const {getPackedInt32Pairs} = goog.require('protobuf.binary.packedInt32TestPairs');
const {getPackedInt64Pairs} = goog.require('protobuf.binary.packedInt64TestPairs');
const {getPackedSfixed32Pairs} = goog.require('protobuf.binary.packedSfixed32TestPairs');
const {getPackedSfixed64Pairs} = goog.require('protobuf.binary.packedSfixed64TestPairs');
const {getPackedSint32Pairs} = goog.require('protobuf.binary.packedSint32TestPairs');
const {getPackedSint64Pairs} = goog.require('protobuf.binary.packedSint64TestPairs');
const {getPackedUint32Pairs} = goog.require('protobuf.binary.packedUint32TestPairs');
const {getSfixed32Pairs} = goog.require('protobuf.binary.sfixed32TestPairs');
const {getSfixed64Pairs} = goog.require('protobuf.binary.sfixed64TestPairs');
const {getSint32Pairs} = goog.require('protobuf.binary.sint32TestPairs');
const {getSint64Pairs} = goog.require('protobuf.binary.sint64TestPairs');
const {getUint32Pairs} = goog.require('protobuf.binary.uint32TestPairs');
/******************************************************************************
* Optional FUNCTIONS
******************************************************************************/
describe('Read bool does', () => {
for (const pair of getBoolPairs()) {
it(`decode ${pair.name}`, () => {
if (pair.error && CHECK_CRITICAL_STATE) {
expect(() => reader.readBool(pair.bufferDecoder, 0)).toThrow();
} else {
const d = reader.readBool(
pair.bufferDecoder, pair.bufferDecoder.startIndex());
expect(d).toEqual(pair.boolValue);
}
});
}
});
describe('readBytes does', () => {
it('throw exception if data is too short', () => {
const bufferDecoder = createBufferDecoder();
expect(() => reader.readBytes(bufferDecoder, 0)).toThrow();
});
it('read bytes by index', () => {
const bufferDecoder = createBufferDecoder(3, 1, 2, 3);
const byteString = reader.readBytes(bufferDecoder, 0);
expect(ByteString.fromArrayBuffer(new Uint8Array([1, 2, 3]).buffer))
.toEqual(byteString);
});
});
describe('readDouble does', () => {
it('throw exception if data is too short', () => {
const bufferDecoder = createBufferDecoder();
expect(() => reader.readDouble(bufferDecoder, 0)).toThrow();
});
for (const pair of getDoublePairs()) {
it(`decode ${pair.name}`, () => {
const d = reader.readDouble(pair.bufferDecoder, 0);
expect(d).toEqual(pair.doubleValue);
});
}
});
describe('readFixed32 does', () => {
it('throw exception if data is too short', () => {
const bufferDecoder = createBufferDecoder();
expect(() => reader.readFixed32(bufferDecoder, 0)).toThrow();
});
for (const pair of getFixed32Pairs()) {
it(`decode ${pair.name}`, () => {
const d = reader.readFixed32(pair.bufferDecoder, 0);
expect(d).toEqual(pair.intValue);
});
}
});
describe('readFloat does', () => {
it('throw exception if data is too short', () => {
const bufferDecoder = createBufferDecoder();
expect(() => reader.readFloat(bufferDecoder, 0)).toThrow();
});
for (const pair of getFloatPairs()) {
it(`decode ${pair.name}`, () => {
const d = reader.readFloat(pair.bufferDecoder, 0);
expect(d).toEqual(Math.fround(pair.floatValue));
});
}
});
describe('readInt32 does', () => {
it('throw exception if data is too short', () => {
const bufferDecoder = createBufferDecoder(0x80);
expect(() => reader.readInt32(bufferDecoder, 0)).toThrow();
});
for (const pair of getInt32Pairs()) {
it(`decode ${pair.name}`, () => {
if (pair.error && CHECK_CRITICAL_STATE) {
expect(() => reader.readInt32(pair.bufferDecoder, 0)).toThrow();
} else {
const d = reader.readInt32(pair.bufferDecoder, 0);
expect(d).toEqual(pair.intValue);
}
});
}
});
describe('readSfixed32 does', () => {
it('throw exception if data is too short', () => {
const bufferDecoder = createBufferDecoder(0x80);
expect(() => reader.readSfixed32(bufferDecoder, 0)).toThrow();
});
for (const pair of getSfixed32Pairs()) {
it(`decode ${pair.name}`, () => {
const d = reader.readSfixed32(pair.bufferDecoder, 0);
expect(d).toEqual(pair.intValue);
});
}
});
describe('readSfixed64 does', () => {
it('throw exception if data is too short', () => {
const bufferDecoder = createBufferDecoder(0x80);
expect(() => reader.readSfixed64(bufferDecoder, 0)).toThrow();
});
for (const pair of getSfixed64Pairs()) {
it(`decode ${pair.name}`, () => {
const d = reader.readSfixed64(pair.bufferDecoder, 0);
expect(d).toEqual(pair.longValue);
});
}
});
describe('readSint32 does', () => {
it('throw exception if data is too short', () => {
const bufferDecoder = createBufferDecoder(0x80);
expect(() => reader.readSint32(bufferDecoder, 0)).toThrow();
});
for (const pair of getSint32Pairs()) {
it(`decode ${pair.name}`, () => {
if (pair.error && CHECK_CRITICAL_STATE) {
expect(() => reader.readSint32(pair.bufferDecoder, 0)).toThrow();
} else {
const d = reader.readSint32(pair.bufferDecoder, 0);
expect(d).toEqual(pair.intValue);
}
});
}
});
describe('readInt64 does', () => {
it('throw exception if data is too short', () => {
const bufferDecoder = createBufferDecoder(0x80);
expect(() => reader.readInt64(bufferDecoder, 0)).toThrow();
});
for (const pair of getInt64Pairs()) {
it(`decode ${pair.name}`, () => {
if (pair.error && CHECK_CRITICAL_STATE) {
expect(() => reader.readInt64(pair.bufferDecoder, 0)).toThrow();
} else {
const d = reader.readInt64(pair.bufferDecoder, 0);
expect(d).toEqual(pair.longValue);
}
});
}
});
describe('readSint64 does', () => {
it('throw exception if data is too short', () => {
const bufferDecoder = createBufferDecoder(0x80);
expect(() => reader.readSint64(bufferDecoder, 0)).toThrow();
});
for (const pair of getSint64Pairs()) {
it(`decode ${pair.name}`, () => {
if (pair.error && CHECK_CRITICAL_STATE) {
expect(() => reader.readSint64(pair.bufferDecoder, 0)).toThrow();
} else {
const d = reader.readSint64(pair.bufferDecoder, 0);
expect(d).toEqual(pair.longValue);
}
});
}
});
describe('readUint32 does', () => {
it('throw exception if data is too short', () => {
const bufferDecoder = createBufferDecoder(0x80);
expect(() => reader.readUint32(bufferDecoder, 0)).toThrow();
});
for (const pair of getUint32Pairs()) {
if (!pair.skip_reader) {
it(`decode ${pair.name}`, () => {
if (pair.error && CHECK_CRITICAL_STATE) {
expect(() => reader.readUint32(pair.bufferDecoder, 0)).toThrow();
} else {
const d = reader.readUint32(pair.bufferDecoder, 0);
expect(d).toEqual(pair.intValue);
}
});
}
}
});
/**
*
* @param {string} s
* @return {!Uint8Array}
*/
function encodeString(s) {
if (typeof TextEncoder !== 'undefined') {
const textEncoder = new TextEncoder('utf-8');
return textEncoder.encode(s);
} else {
return encode(s);
}
}
/** @param {string} s */
function expectEncodedStringToMatch(s) {
const array = encodeString(s);
const length = array.length;
if (length > 127) {
throw new Error('Test only works for strings shorter than 128');
}
const encodedArray = new Uint8Array(length + 1);
encodedArray[0] = length;
encodedArray.set(array, 1);
const bufferDecoder = BufferDecoder.fromArrayBuffer(encodedArray.buffer);
expect(reader.readString(bufferDecoder, 0)).toEqual(s);
}
describe('readString does', () => {
it('return empty string for zero length string', () => {
const s = reader.readString(createBufferDecoder(0x00), 0);
expect(s).toEqual('');
});
it('decode random strings', () => {
// 1 byte strings
expectEncodedStringToMatch('hello');
expectEncodedStringToMatch('HELLO1!');
// 2 byte String
expectEncodedStringToMatch('©');
// 3 byte string
expectEncodedStringToMatch('❄');
// 4 byte string
expectEncodedStringToMatch('😁');
});
it('decode 1 byte strings', () => {
for (let i = 0; i < 0x80; i++) {
const s = String.fromCharCode(i);
expectEncodedStringToMatch(s);
}
});
it('decode 2 byte strings', () => {
for (let i = 0xC0; i < 0x7FF; i++) {
const s = String.fromCharCode(i);
expectEncodedStringToMatch(s);
}
});
it('decode 3 byte strings', () => {
for (let i = 0x7FF; i < 0x8FFF; i++) {
const s = String.fromCharCode(i);
expectEncodedStringToMatch(s);
}
});
it('throw exception on invalid bytes', () => {
// This test will only succeed with the native TextDecoder since
// our polyfill does not do any validation. IE10 and IE11 don't support
// TextDecoder.
// TODO: Remove this check once we no longer need to support IE
if (typeof TextDecoder !== 'undefined') {
expect(
() => reader.readString(
createBufferDecoder(0x01, /* invalid utf data point*/ 0xFF), 0))
.toThrow();
}
});
it('throw exception if data is too short', () => {
const array = createBufferDecoder(0x02, '?'.charCodeAt(0));
expect(() => reader.readString(array, 0)).toThrow();
});
});
/******************************************************************************
* REPEATED FUNCTIONS
******************************************************************************/
describe('readPackedBool does', () => {
for (const pair of getPackedBoolPairs()) {
it(`decode ${pair.name}`, () => {
const d = reader.readPackedBool(pair.bufferDecoder, 0);
expect(d).toEqual(pair.boolValues);
});
}
});
describe('readPackedDouble does', () => {
for (const pair of getPackedDoublePairs()) {
it(`decode ${pair.name}`, () => {
const d = reader.readPackedDouble(pair.bufferDecoder, 0);
expect(d).toEqual(pair.doubleValues);
});
}
});
describe('readPackedFixed32 does', () => {
for (const pair of getPackedFixed32Pairs()) {
it(`decode ${pair.name}`, () => {
const d = reader.readPackedFixed32(pair.bufferDecoder, 0);
expect(d).toEqual(pair.fixed32Values);
});
}
});
describe('readPackedFloat does', () => {
for (const pair of getPackedFloatPairs()) {
it(`decode ${pair.name}`, () => {
const d = reader.readPackedFloat(pair.bufferDecoder, 0);
expect(d).toEqual(pair.floatValues);
});
}
});
describe('readPackedInt32 does', () => {
for (const pair of getPackedInt32Pairs()) {
it(`decode ${pair.name}`, () => {
const d = reader.readPackedInt32(pair.bufferDecoder, 0);
expect(d).toEqual(pair.int32Values);
});
}
});
describe('readPackedInt64 does', () => {
for (const pair of getPackedInt64Pairs()) {
it(`decode ${pair.name}`, () => {
const d = reader.readPackedInt64(pair.bufferDecoder, 0);
expect(d).toEqual(pair.int64Values);
});
}
});
describe('readPackedSfixed32 does', () => {
for (const pair of getPackedSfixed32Pairs()) {
it(`decode ${pair.name}`, () => {
const d = reader.readPackedSfixed32(pair.bufferDecoder, 0);
expect(d).toEqual(pair.sfixed32Values);
});
}
});
describe('readPackedSfixed64 does', () => {
for (const pair of getPackedSfixed64Pairs()) {
it(`decode ${pair.name}`, () => {
const d = reader.readPackedSfixed64(pair.bufferDecoder, 0);
expect(d).toEqual(pair.sfixed64Values);
});
}
});
describe('readPackedSint32 does', () => {
for (const pair of getPackedSint32Pairs()) {
it(`decode ${pair.name}`, () => {
const d = reader.readPackedSint32(pair.bufferDecoder, 0);
expect(d).toEqual(pair.sint32Values);
});
}
});
describe('readPackedSint64 does', () => {
for (const pair of getPackedSint64Pairs()) {
it(`decode ${pair.name}`, () => {
const d = reader.readPackedSint64(pair.bufferDecoder, 0);
expect(d).toEqual(pair.sint64Values);
});
}
});
describe('readPackedUint32 does', () => {
for (const pair of getPackedUint32Pairs()) {
it(`decode ${pair.name}`, () => {
const d = reader.readPackedUint32(pair.bufferDecoder, 0);
expect(d).toEqual(pair.uint32Values);
});
}
});

View File

@ -0,0 +1,46 @@
/**
* @fileoverview Test data for sfixed32 encoding and decoding.
*/
goog.module('protobuf.binary.sfixed32TestPairs');
const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
/**
* An array of Pairs of int values and their bit representation.
* This is used to test encoding and decoding from/to the protobuf wire format.
* @return {!Array<{name: string, intValue: number, bufferDecoder:
* !BufferDecoder}>}
*/
function getSfixed32Pairs() {
const sfixed32Pairs = [
{
name: 'zero',
intValue: 0,
bufferDecoder: createBufferDecoder(0x00, 0x00, 0x00, 0x00),
},
{
name: 'one',
intValue: 1,
bufferDecoder: createBufferDecoder(0x01, 0x00, 0x00, 0x00)
},
{
name: 'minus one',
intValue: -1,
bufferDecoder: createBufferDecoder(0xFF, 0xFF, 0xFF, 0xFF),
},
{
name: 'max int 2^31 -1',
intValue: Math.pow(2, 31) - 1,
bufferDecoder: createBufferDecoder(0xFF, 0xFF, 0xFF, 0x7F)
},
{
name: 'min int -2^31',
intValue: -Math.pow(2, 31),
bufferDecoder: createBufferDecoder(0x00, 0x00, 0x00, 0x80)
},
];
return [...sfixed32Pairs];
}
exports = {getSfixed32Pairs};

View File

@ -0,0 +1,52 @@
/**
* @fileoverview Test data for sfixed32 encoding and decoding.
*/
goog.module('protobuf.binary.sfixed64TestPairs');
const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
const Int64 = goog.require('protobuf.Int64');
const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
/**
* An array of Pairs of int values and their bit representation.
* This is used to test encoding and decoding from/to the protobuf wire format.
* @return {!Array<{name: string, longValue: !Int64, bufferDecoder:
* !BufferDecoder}>}
*/
function getSfixed64Pairs() {
const sfixed64Pairs = [
{
name: 'zero',
longValue: Int64.fromInt(0),
bufferDecoder:
createBufferDecoder(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
},
{
name: 'one',
longValue: Int64.fromInt(1),
bufferDecoder:
createBufferDecoder(0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
},
{
name: 'minus one',
longValue: Int64.fromInt(-1),
bufferDecoder:
createBufferDecoder(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF),
},
{
name: 'max int 2^63 -1',
longValue: Int64.getMaxValue(),
bufferDecoder:
createBufferDecoder(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F)
},
{
name: 'min int -2^63',
longValue: Int64.getMinValue(),
bufferDecoder:
createBufferDecoder(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80)
},
];
return [...sfixed64Pairs];
}
exports = {getSfixed64Pairs};

View File

@ -0,0 +1,57 @@
/**
* @fileoverview Test data for int32 encoding and decoding.
*/
goog.module('protobuf.binary.sint32TestPairs');
const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
/**
* An array of Pairs of float values and their bit representation.
* This is used to test encoding and decoding from/to the protobuf wire format.
* @return {!Array<{name: string, intValue:number, bufferDecoder:
* !BufferDecoder, error: ?boolean, skip_writer: ?boolean}>}
*/
function getSint32Pairs() {
const sint32Pairs = [
{
name: 'zero',
intValue: 0,
bufferDecoder: createBufferDecoder(0x00),
},
{
name: 'one ',
intValue: 1,
bufferDecoder: createBufferDecoder(0x02),
},
{
name: 'minus one',
intValue: -1,
bufferDecoder: createBufferDecoder(0x01),
},
{
name: 'two',
intValue: 2,
bufferDecoder: createBufferDecoder(0x04),
},
{
name: 'minus two',
intValue: -2,
bufferDecoder: createBufferDecoder(0x03),
},
{
name: 'int 2^31 - 1',
intValue: Math.pow(2, 31) - 1,
bufferDecoder: createBufferDecoder(0xFE, 0xFF, 0xFF, 0xFF, 0x0F),
},
{
name: '-2^31',
intValue: -Math.pow(2, 31),
bufferDecoder: createBufferDecoder(0xFF, 0xFF, 0xFF, 0xFF, 0x0F),
},
];
return [...sint32Pairs];
}
exports = {getSint32Pairs};

View File

@ -0,0 +1,60 @@
/**
* @fileoverview Test data for sint64 encoding and decoding.
*/
goog.module('protobuf.binary.sint64TestPairs');
const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
const Int64 = goog.require('protobuf.Int64');
const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
/**
* An array of Pairs of float values and their bit representation.
* This is used to test encoding and decoding from/to the protobuf wire format.
* @return {!Array<{name: string, longValue: !Int64, bufferDecoder:
* !BufferDecoder, error: ?boolean, skip_writer: ?boolean}>}
*/
function getSint64Pairs() {
const sint64Pairs = [
{
name: 'zero',
longValue: Int64.fromInt(0),
bufferDecoder: createBufferDecoder(0x00),
},
{
name: 'one ',
longValue: Int64.fromInt(1),
bufferDecoder: createBufferDecoder(0x02),
},
{
name: 'minus one',
longValue: Int64.fromInt(-1),
bufferDecoder: createBufferDecoder(0x01),
},
{
name: 'two',
longValue: Int64.fromInt(2),
bufferDecoder: createBufferDecoder(0x04),
},
{
name: 'minus two',
longValue: Int64.fromInt(-2),
bufferDecoder: createBufferDecoder(0x03),
},
{
name: 'min value',
longValue: Int64.getMinValue(),
bufferDecoder: createBufferDecoder(
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01),
},
{
name: 'max value',
longValue: Int64.getMaxValue(),
bufferDecoder: createBufferDecoder(
0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01),
},
];
return [...sint64Pairs];
}
exports = {getSint64Pairs};

View File

@ -0,0 +1,67 @@
goog.module('protobuf.runtime.Storage');
/**
* Interface for getting and storing fields of a protobuf message.
*
* @interface
* @package
* @template FieldType
*/
class Storage {
/**
* Returns the pivot value.
*
* @return {number}
*/
getPivot() {}
/**
* Sets a field in the specified field number.
*
* @param {number} fieldNumber
* @param {!FieldType} field
*/
set(fieldNumber, field) {}
/**
* Returns a field at the specified field number.
*
* @param {number} fieldNumber
* @return {!FieldType|undefined}
*/
get(fieldNumber) {}
/**
* Deletes a field from the specified field number.
*
* @param {number} fieldNumber
*/
delete(fieldNumber) {}
/**
* Executes the provided function once for each field.
*
* @param {function(!FieldType, number): void} callback
*/
forEach(callback) {}
/**
* Creates a shallow copy of the storage.
*
* @return {!Storage}
*/
shallowCopy() {}
}
/**
* 85% of the proto fields have a field number <= 24:
* https://plx.corp.google.com/scripts2/script_5d._f02af6_0000_23b1_a15f_001a1139dd02
*
* @type {number}
*/
// LINT.IfChange
Storage.DEFAULT_PIVOT = 24;
// LINT.ThenChange(//depot/google3/third_party/protobuf/javascript/runtime/kernel/binary_storage_test.js,
// //depot/google3/net/proto2/contrib/js_proto/internal/kernel_message_generator.cc)
exports = Storage;

View File

@ -0,0 +1,144 @@
goog.module('protobuf.binary.tag');
const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
const WireType = goog.require('protobuf.binary.WireType');
const {checkCriticalElementIndex, checkCriticalState} = goog.require('protobuf.internal.checks');
/**
* Returns wire type stored in a tag.
* Protos store the wire type as the first 3 bit of a tag.
* @param {number} tag
* @return {!WireType}
*/
function tagToWireType(tag) {
return /** @type {!WireType} */ (tag & 0x07);
}
/**
* Returns the field number stored in a tag.
* Protos store the field number in the upper 29 bits of a 32 bit number.
* @param {number} tag
* @return {number}
*/
function tagToFieldNumber(tag) {
return tag >>> 3;
}
/**
* Combines wireType and fieldNumber into a tag.
* @param {!WireType} wireType
* @param {number} fieldNumber
* @return {number}
*/
function createTag(wireType, fieldNumber) {
return (fieldNumber << 3 | wireType) >>> 0;
}
/**
* Returns the length, in bytes, of the field in the tag stream, less the tag
* itself.
* Note: This moves the cursor in the bufferDecoder.
* @param {!BufferDecoder} bufferDecoder
* @param {number} start
* @param {!WireType} wireType
* @param {number} fieldNumber
* @return {number}
* @private
*/
function getTagLength(bufferDecoder, start, wireType, fieldNumber) {
bufferDecoder.setCursor(start);
skipField(bufferDecoder, wireType, fieldNumber);
return bufferDecoder.cursor() - start;
}
/**
* @param {number} value
* @return {number}
*/
function get32BitVarintLength(value) {
if (value < 0) {
return 5;
}
let size = 1;
while (value >= 128) {
size++;
value >>>= 7;
}
return size;
}
/**
* Skips over a field.
* Note: If the field is a start group the entire group will be skipped, placing
* the cursor onto the next field.
* @param {!BufferDecoder} bufferDecoder
* @param {!WireType} wireType
* @param {number} fieldNumber
*/
function skipField(bufferDecoder, wireType, fieldNumber) {
switch (wireType) {
case WireType.VARINT:
checkCriticalElementIndex(
bufferDecoder.cursor(), bufferDecoder.endIndex());
bufferDecoder.skipVarint();
return;
case WireType.FIXED64:
bufferDecoder.skip(8);
return;
case WireType.DELIMITED:
checkCriticalElementIndex(
bufferDecoder.cursor(), bufferDecoder.endIndex());
const length = bufferDecoder.getUnsignedVarint32();
bufferDecoder.skip(length);
return;
case WireType.START_GROUP:
const foundGroup = skipGroup_(bufferDecoder, fieldNumber);
checkCriticalState(foundGroup, 'No end group found.');
return;
case WireType.FIXED32:
bufferDecoder.skip(4);
return;
default:
throw new Error(`Unexpected wire type: ${wireType}`);
}
}
/**
* Skips over fields until it finds the end of a given group consuming the stop
* group tag.
* @param {!BufferDecoder} bufferDecoder
* @param {number} groupFieldNumber
* @return {boolean} Whether the end group tag was found.
* @private
*/
function skipGroup_(bufferDecoder, groupFieldNumber) {
// On a start group we need to keep skipping fields until we find a
// corresponding stop group
// Note: Since we are calling skipField from here nested groups will be
// handled by recursion of this method and thus we will not see a nested
// STOP GROUP here unless there is something wrong with the input data.
while (bufferDecoder.hasNext()) {
const tag = bufferDecoder.getUnsignedVarint32();
const wireType = tagToWireType(tag);
const fieldNumber = tagToFieldNumber(tag);
if (wireType === WireType.END_GROUP) {
checkCriticalState(
groupFieldNumber === fieldNumber,
`Expected stop group for fieldnumber ${groupFieldNumber} not found.`);
return true;
} else {
skipField(bufferDecoder, wireType, fieldNumber);
}
}
return false;
}
exports = {
createTag,
get32BitVarintLength,
getTagLength,
skipField,
tagToWireType,
tagToFieldNumber,
};

View File

@ -0,0 +1,221 @@
/**
* @fileoverview Tests for tag.js.
*/
goog.module('protobuf.binary.TagTests');
const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
const WireType = goog.require('protobuf.binary.WireType');
const {CHECK_CRITICAL_STATE} = goog.require('protobuf.internal.checks');
const {createTag, get32BitVarintLength, skipField, tagToFieldNumber, tagToWireType} = goog.require('protobuf.binary.tag');
goog.setTestOnly();
/**
* @param {...number} bytes
* @return {!ArrayBuffer}
*/
function createArrayBuffer(...bytes) {
return new Uint8Array(bytes).buffer;
}
describe('skipField', () => {
it('skips varints', () => {
const bufferDecoder =
BufferDecoder.fromArrayBuffer(createArrayBuffer(0x80, 0x00));
skipField(bufferDecoder, WireType.VARINT, 1);
expect(bufferDecoder.cursor()).toBe(2);
});
it('throws for out of bounds varints', () => {
const bufferDecoder =
BufferDecoder.fromArrayBuffer(createArrayBuffer(0x80, 0x00));
bufferDecoder.setCursor(2);
if (CHECK_CRITICAL_STATE) {
expect(() => skipField(bufferDecoder, WireType.VARINT, 1)).toThrowError();
}
});
it('skips fixed64', () => {
const bufferDecoder = BufferDecoder.fromArrayBuffer(
createArrayBuffer(0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
skipField(bufferDecoder, WireType.FIXED64, 1);
expect(bufferDecoder.cursor()).toBe(8);
});
it('throws for fixed64 if length is too short', () => {
const bufferDecoder =
BufferDecoder.fromArrayBuffer(createArrayBuffer(0x80, 0x00));
if (CHECK_CRITICAL_STATE) {
expect(() => skipField(bufferDecoder, WireType.FIXED64, 1))
.toThrowError();
}
});
it('skips fixed32', () => {
const bufferDecoder = BufferDecoder.fromArrayBuffer(
createArrayBuffer(0x80, 0x00, 0x00, 0x00));
skipField(bufferDecoder, WireType.FIXED32, 1);
expect(bufferDecoder.cursor()).toBe(4);
});
it('throws for fixed32 if length is too short', () => {
const bufferDecoder =
BufferDecoder.fromArrayBuffer(createArrayBuffer(0x80, 0x00));
if (CHECK_CRITICAL_STATE) {
expect(() => skipField(bufferDecoder, WireType.FIXED32, 1))
.toThrowError();
}
});
it('skips length delimited', () => {
const bufferDecoder = BufferDecoder.fromArrayBuffer(
createArrayBuffer(0x03, 0x00, 0x00, 0x00));
skipField(bufferDecoder, WireType.DELIMITED, 1);
expect(bufferDecoder.cursor()).toBe(4);
});
it('throws for length delimited if length is too short', () => {
const bufferDecoder =
BufferDecoder.fromArrayBuffer(createArrayBuffer(0x03, 0x00, 0x00));
if (CHECK_CRITICAL_STATE) {
expect(() => skipField(bufferDecoder, WireType.DELIMITED, 1))
.toThrowError();
}
});
it('skips groups', () => {
const bufferDecoder = BufferDecoder.fromArrayBuffer(
createArrayBuffer(0x0B, 0x08, 0x01, 0x0C));
bufferDecoder.setCursor(1);
skipField(bufferDecoder, WireType.START_GROUP, 1);
expect(bufferDecoder.cursor()).toBe(4);
});
it('skips group in group', () => {
const buffer = createArrayBuffer(
0x0B, // start outer
0x10, 0x01, // field: 2, value: 1
0x0B, // start inner group
0x10, 0x01, // payload inner group
0x0C, // stop inner group
0x0C // end outer
);
const bufferDecoder = BufferDecoder.fromArrayBuffer(buffer);
bufferDecoder.setCursor(1);
skipField(bufferDecoder, WireType.START_GROUP, 1);
expect(bufferDecoder.cursor()).toBe(8);
});
it('throws for group if length is too short', () => {
// no closing group
const bufferDecoder =
BufferDecoder.fromArrayBuffer(createArrayBuffer(0x0B, 0x00, 0x00));
if (CHECK_CRITICAL_STATE) {
expect(() => skipField(bufferDecoder, WireType.START_GROUP, 1))
.toThrowError();
}
});
});
describe('tagToWireType', () => {
it('decodes numbers ', () => {
// simple numbers
expect(tagToWireType(0x00)).toBe(WireType.VARINT);
expect(tagToWireType(0x01)).toBe(WireType.FIXED64);
expect(tagToWireType(0x02)).toBe(WireType.DELIMITED);
expect(tagToWireType(0x03)).toBe(WireType.START_GROUP);
expect(tagToWireType(0x04)).toBe(WireType.END_GROUP);
expect(tagToWireType(0x05)).toBe(WireType.FIXED32);
// upper bits should not matter
expect(tagToWireType(0x08)).toBe(WireType.VARINT);
expect(tagToWireType(0x09)).toBe(WireType.FIXED64);
expect(tagToWireType(0x0A)).toBe(WireType.DELIMITED);
expect(tagToWireType(0x0B)).toBe(WireType.START_GROUP);
expect(tagToWireType(0x0C)).toBe(WireType.END_GROUP);
expect(tagToWireType(0x0D)).toBe(WireType.FIXED32);
// upper bits should not matter
expect(tagToWireType(0xF8)).toBe(WireType.VARINT);
expect(tagToWireType(0xF9)).toBe(WireType.FIXED64);
expect(tagToWireType(0xFA)).toBe(WireType.DELIMITED);
expect(tagToWireType(0xFB)).toBe(WireType.START_GROUP);
expect(tagToWireType(0xFC)).toBe(WireType.END_GROUP);
expect(tagToWireType(0xFD)).toBe(WireType.FIXED32);
// negative numbers work
expect(tagToWireType(-8)).toBe(WireType.VARINT);
expect(tagToWireType(-7)).toBe(WireType.FIXED64);
expect(tagToWireType(-6)).toBe(WireType.DELIMITED);
expect(tagToWireType(-5)).toBe(WireType.START_GROUP);
expect(tagToWireType(-4)).toBe(WireType.END_GROUP);
expect(tagToWireType(-3)).toBe(WireType.FIXED32);
});
});
describe('tagToFieldNumber', () => {
it('returns fieldNumber', () => {
expect(tagToFieldNumber(0x08)).toBe(1);
expect(tagToFieldNumber(0x09)).toBe(1);
expect(tagToFieldNumber(0x10)).toBe(2);
expect(tagToFieldNumber(0x12)).toBe(2);
});
});
describe('createTag', () => {
it('combines fieldNumber and wireType', () => {
expect(createTag(WireType.VARINT, 1)).toBe(0x08);
expect(createTag(WireType.FIXED64, 1)).toBe(0x09);
expect(createTag(WireType.DELIMITED, 1)).toBe(0x0A);
expect(createTag(WireType.START_GROUP, 1)).toBe(0x0B);
expect(createTag(WireType.END_GROUP, 1)).toBe(0x0C);
expect(createTag(WireType.FIXED32, 1)).toBe(0x0D);
expect(createTag(WireType.VARINT, 2)).toBe(0x10);
expect(createTag(WireType.FIXED64, 2)).toBe(0x11);
expect(createTag(WireType.DELIMITED, 2)).toBe(0x12);
expect(createTag(WireType.START_GROUP, 2)).toBe(0x13);
expect(createTag(WireType.END_GROUP, 2)).toBe(0x14);
expect(createTag(WireType.FIXED32, 2)).toBe(0x15);
expect(createTag(WireType.VARINT, 0x1FFFFFFF)).toBe(0xFFFFFFF8 >>> 0);
expect(createTag(WireType.FIXED64, 0x1FFFFFFF)).toBe(0xFFFFFFF9 >>> 0);
expect(createTag(WireType.DELIMITED, 0x1FFFFFFF)).toBe(0xFFFFFFFA >>> 0);
expect(createTag(WireType.START_GROUP, 0x1FFFFFFF)).toBe(0xFFFFFFFB >>> 0);
expect(createTag(WireType.END_GROUP, 0x1FFFFFFF)).toBe(0xFFFFFFFC >>> 0);
expect(createTag(WireType.FIXED32, 0x1FFFFFFF)).toBe(0xFFFFFFFD >>> 0);
});
});
describe('get32BitVarintLength', () => {
it('length of tag', () => {
expect(get32BitVarintLength(0)).toBe(1);
expect(get32BitVarintLength(1)).toBe(1);
expect(get32BitVarintLength(1)).toBe(1);
expect(get32BitVarintLength(Math.pow(2, 7) - 1)).toBe(1);
expect(get32BitVarintLength(Math.pow(2, 7))).toBe(2);
expect(get32BitVarintLength(Math.pow(2, 14) - 1)).toBe(2);
expect(get32BitVarintLength(Math.pow(2, 14))).toBe(3);
expect(get32BitVarintLength(Math.pow(2, 21) - 1)).toBe(3);
expect(get32BitVarintLength(Math.pow(2, 21))).toBe(4);
expect(get32BitVarintLength(Math.pow(2, 28) - 1)).toBe(4);
expect(get32BitVarintLength(Math.pow(2, 28))).toBe(5);
expect(get32BitVarintLength(Math.pow(2, 31) - 1)).toBe(5);
expect(get32BitVarintLength(-1)).toBe(5);
expect(get32BitVarintLength(-Math.pow(2, 31))).toBe(5);
expect(get32BitVarintLength(createTag(WireType.VARINT, 0x1fffffff)))
.toBe(5);
expect(get32BitVarintLength(createTag(WireType.FIXED32, 0x1fffffff)))
.toBe(5);
});
});

View File

@ -0,0 +1,116 @@
/**
* @fileoverview A UTF8 decoder.
*/
goog.module('protobuf.binary.textencoding');
const {checkElementIndex} = goog.require('protobuf.internal.checks');
/**
* Combines an array of codePoints into a string.
* @param {!Array<number>} codePoints
* @return {string}
*/
function codePointsToString(codePoints) {
// Performance: http://jsperf.com/string-fromcharcode-test/13
let s = '', i = 0;
const length = codePoints.length;
const BATCH_SIZE = 10000;
while (i < length) {
const end = Math.min(i + BATCH_SIZE, length);
s += String.fromCharCode.apply(null, codePoints.slice(i, end));
i = end;
}
return s;
}
/**
* Decodes raw bytes into a string.
* Supports codepoints from U+0000 up to U+10FFFF.
* (http://en.wikipedia.org/wiki/UTF-8).
* @param {!DataView} bytes
* @return {string}
*/
function decode(bytes) {
let cursor = 0;
const codePoints = [];
while (cursor < bytes.byteLength) {
const c = bytes.getUint8(cursor++);
if (c < 0x80) { // Regular 7-bit ASCII.
codePoints.push(c);
} else if (c < 0xC0) {
// UTF-8 continuation mark. We are out of sync. This
// might happen if we attempted to read a character
// with more than four bytes.
continue;
} else if (c < 0xE0) { // UTF-8 with two bytes.
checkElementIndex(cursor, bytes.byteLength);
const c2 = bytes.getUint8(cursor++);
codePoints.push(((c & 0x1F) << 6) | (c2 & 0x3F));
} else if (c < 0xF0) { // UTF-8 with three bytes.
checkElementIndex(cursor + 1, bytes.byteLength);
const c2 = bytes.getUint8(cursor++);
const c3 = bytes.getUint8(cursor++);
codePoints.push(((c & 0xF) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F));
} else if (c < 0xF8) { // UTF-8 with 4 bytes.
checkElementIndex(cursor + 2, bytes.byteLength);
const c2 = bytes.getUint8(cursor++);
const c3 = bytes.getUint8(cursor++);
const c4 = bytes.getUint8(cursor++);
// Characters written on 4 bytes have 21 bits for a codepoint.
// We can't fit that on 16bit characters, so we use surrogates.
let codepoint = ((c & 0x07) << 18) | ((c2 & 0x3F) << 12) |
((c3 & 0x3F) << 6) | (c4 & 0x3F);
// Surrogates formula from wikipedia.
// 1. Subtract 0x10000 from codepoint
codepoint -= 0x10000;
// 2. Split this into the high 10-bit value and the low 10-bit value
// 3. Add 0xD800 to the high value to form the high surrogate
// 4. Add 0xDC00 to the low value to form the low surrogate:
const low = (codepoint & 0x3FF) + 0xDC00;
const high = ((codepoint >> 10) & 0x3FF) + 0xD800;
codePoints.push(high, low);
}
}
return codePointsToString(codePoints);
}
/**
* Writes a UTF16 JavaScript string to the buffer encoded as UTF8.
* @param {string} value The string to write.
* @return {!Uint8Array} An array containing the encoded bytes.
*/
function encode(value) {
const buffer = [];
for (let i = 0; i < value.length; i++) {
const c1 = value.charCodeAt(i);
if (c1 < 0x80) {
buffer.push(c1);
} else if (c1 < 0x800) {
buffer.push((c1 >> 6) | 0xC0);
buffer.push((c1 & 0x3F) | 0x80);
} else if (c1 < 0xD800 || c1 >= 0xE000) {
buffer.push((c1 >> 12) | 0xE0);
buffer.push(((c1 >> 6) & 0x3F) | 0x80);
buffer.push((c1 & 0x3F) | 0x80);
} else {
// surrogate pair
i++;
checkElementIndex(i, value.length);
const c2 = value.charCodeAt(i);
const paired = 0x10000 + (((c1 & 0x3FF) << 10) | (c2 & 0x3FF));
buffer.push((paired >> 18) | 0xF0);
buffer.push(((paired >> 12) & 0x3F) | 0x80);
buffer.push(((paired >> 6) & 0x3F) | 0x80);
buffer.push((paired & 0x3F) | 0x80);
}
}
return new Uint8Array(buffer);
}
exports = {
decode,
encode,
};

View File

@ -0,0 +1,113 @@
/**
* @fileoverview Tests for textdecoder.js.
*/
goog.module('protobuf.binary.TextDecoderTest');
goog.setTestOnly();
const {decode, encode} = goog.require('protobuf.binary.textencoding');
describe('Decode does', () => {
it('return empty string for empty array', () => {
expect(decode(new DataView(new ArrayBuffer(0)))).toEqual('');
});
it('throw on null being passed', () => {
expect(() => decode(/** @type {!DataView} */ (/** @type {*} */ (null))))
.toThrow();
});
});
describe('Encode does', () => {
it('return empty array for empty string', () => {
expect(encode('')).toEqual(new Uint8Array(0));
});
it('throw on null being passed', () => {
expect(() => encode(/** @type {string} */ (/** @type {*} */ (null))))
.toThrow();
});
});
/** @const {!TextEncoder} */
const textEncoder = new TextEncoder('utf-8');
/**
* A Pair of string and Uint8Array representing the same data.
* Each pair has the string value and its utf-8 bytes.
*/
class Pair {
/**
* Constructs a pair from a given string.
* @param {string} s
* @return {!Pair}
*/
static fromString(s) {
return new Pair(s, textEncoder.encode(s).buffer);
}
/**
* Constructs a pair from a given charCode.
* @param {number} charCode
* @return {!Pair}
*/
static fromCharCode(charCode) {
return Pair.fromString(String.fromCharCode(charCode));
}
/**
* @param {string} stringValue
* @param {!ArrayBuffer} bytes
* @private
*/
constructor(stringValue, bytes) {
/** @const @private {string} */
this.stringValue_ = stringValue;
/** @const @private {!ArrayBuffer} */
this.bytes_ = bytes;
}
/** Ensures that a given pair encodes and decodes round trip*/
expectPairToMatch() {
expect(decode(new DataView(this.bytes_))).toEqual(this.stringValue_);
expect(encode(this.stringValue_)).toEqual(new Uint8Array(this.bytes_));
}
}
describe('textencoding does', () => {
it('works for empty string', () => {
Pair.fromString('').expectPairToMatch();
});
it('decode and encode random strings', () => {
// 1 byte strings
Pair.fromString('hello').expectPairToMatch();
Pair.fromString('HELLO1!');
// 2 byte String
Pair.fromString('©').expectPairToMatch();
// 3 byte string
Pair.fromString('❄').expectPairToMatch();
// 4 byte string
Pair.fromString('😁').expectPairToMatch();
});
it('decode and encode 1 byte strings', () => {
for (let i = 0; i < 0x80; i++) {
Pair.fromCharCode(i).expectPairToMatch();
}
});
it('decode and encode 2 byte strings', () => {
for (let i = 0xC0; i < 0x7FF; i++) {
Pair.fromCharCode(i).expectPairToMatch();
}
});
it('decode and encode 3 byte strings', () => {
for (let i = 0x7FF; i < 0x8FFF; i++) {
Pair.fromCharCode(i).expectPairToMatch();
}
});
});

View File

@ -0,0 +1,116 @@
/**
* @fileoverview Helper methods for typed arrays.
*/
goog.module('protobuf.binary.typedArrays');
const {assert} = goog.require('goog.asserts');
/**
* @param {!ArrayBuffer} buffer1
* @param {!ArrayBuffer} buffer2
* @return {boolean}
*/
function arrayBufferEqual(buffer1, buffer2) {
if (!buffer1 || !buffer2) {
throw new Error('Buffer shouldn\'t be empty');
}
const array1 = new Uint8Array(buffer1);
const array2 = new Uint8Array(buffer2);
return uint8ArrayEqual(array1, array2);
}
/**
* @param {!Uint8Array} array1
* @param {!Uint8Array} array2
* @return {boolean}
*/
function uint8ArrayEqual(array1, array2) {
if (array1 === array2) {
return true;
}
if (array1.byteLength !== array2.byteLength) {
return false;
}
for (let i = 0; i < array1.byteLength; i++) {
if (array1[i] !== array2[i]) {
return false;
}
}
return true;
}
/**
* ArrayBuffer.prototype.slice, but fallback to manual copy if missing.
* @param {!ArrayBuffer} buffer
* @param {number} start
* @param {number=} end
* @return {!ArrayBuffer} New array buffer with given the contents of `buffer`.
*/
function arrayBufferSlice(buffer, start, end = undefined) {
// The fallback isn't fully compatible with ArrayBuffer.slice, enforce
// strict requirements on start/end.
// Spec:
// https://www.ecma-international.org/ecma-262/6.0/#sec-arraybuffer.prototype.slice
assert(start >= 0);
assert(end === undefined || (end >= 0 && end <= buffer.byteLength));
assert((end === undefined ? buffer.byteLength : end) >= start);
if (buffer.slice) {
const slicedBuffer = buffer.slice(start, end);
// The ArrayBuffer.prototype.slice function was flawed before iOS 12.2. This
// causes 0-length results when passing undefined or a number greater
// than 32 bits for the optional end value. In these cases, we fall back to
// using our own slice implementation.
// More details: https://bugs.webkit.org/show_bug.cgi?id=185127
if (slicedBuffer.byteLength !== 0 || (start === end)) {
return slicedBuffer;
}
}
const realEnd = end == null ? buffer.byteLength : end;
const length = realEnd - start;
assert(length >= 0);
const view = new Uint8Array(buffer, start, length);
// A TypedArray constructed from another Typed array copies the data.
const clone = new Uint8Array(view);
return clone.buffer;
}
/**
* Returns a new Uint8Array with the size and contents of the given
* ArrayBufferView. ArrayBufferView is an interface implemented by DataView,
* Uint8Array and all typed arrays.
* @param {!ArrayBufferView} view
* @return {!Uint8Array}
*/
function cloneArrayBufferView(view) {
return new Uint8Array(arrayBufferSlice(
view.buffer, view.byteOffset, view.byteOffset + view.byteLength));
}
/**
* Returns a 32 bit number for the corresponding Uint8Array.
* @param {!Uint8Array} array
* @return {number}
*/
function hashUint8Array(array) {
const prime = 31;
let result = 17;
for (let i = 0; i < array.length; i++) {
// '| 0' ensures signed 32 bits
result = (result * prime + array[i]) | 0;
}
return result;
}
exports = {
arrayBufferEqual,
uint8ArrayEqual,
arrayBufferSlice,
cloneArrayBufferView,
hashUint8Array,
};

View File

@ -0,0 +1,191 @@
/**
* @fileoverview Tests for typed_arrays.js.
*/
goog.module('protobuf.binary.typedArraysTest');
const {arrayBufferEqual, arrayBufferSlice, cloneArrayBufferView, hashUint8Array, uint8ArrayEqual} = goog.require('protobuf.binary.typedArrays');
describe('arrayBufferEqual', () => {
it('returns true for empty buffers', () => {
const buffer1 = new ArrayBuffer(0);
const buffer2 = new ArrayBuffer(0);
expect(arrayBufferEqual(buffer1, buffer2)).toBe(true);
});
it('throws for first null buffers', () => {
const buffer = new ArrayBuffer(0);
expect(
() => arrayBufferEqual(
/** @type {!ArrayBuffer} */ (/** @type {*} */ (null)), buffer))
.toThrow();
});
it('throws for second null buffers', () => {
const buffer = new ArrayBuffer(0);
expect(
() => arrayBufferEqual(
buffer, /** @type {!ArrayBuffer} */ (/** @type {*} */ (null))))
.toThrow();
});
it('returns true for arrays with same values', () => {
const array1 = new Uint8Array(4);
array1[0] = 1;
const array2 = new Uint8Array(4);
array2[0] = 1;
expect(arrayBufferEqual(array1.buffer, array2.buffer)).toBe(true);
});
it('returns false for arrays with different values', () => {
const array1 = new Uint8Array(4);
array1[0] = 1;
const array2 = new Uint8Array(4);
array2[0] = 2;
expect(arrayBufferEqual(array1.buffer, array2.buffer)).toBe(false);
});
it('returns true same instance', () => {
const array1 = new Uint8Array(4);
array1[0] = 1;
expect(arrayBufferEqual(array1.buffer, array1.buffer)).toBe(true);
});
});
describe('uint8ArrayEqual', () => {
it('returns true for empty arrays', () => {
const array1 = new Uint8Array(0);
const array2 = new Uint8Array(0);
expect(uint8ArrayEqual(array1, array2)).toBe(true);
});
it('throws for first Uint8Array array', () => {
const array = new Uint8Array(0);
expect(
() => uint8ArrayEqual(
/** @type {!Uint8Array} */ (/** @type {*} */ (null)), array))
.toThrow();
});
it('throws for second null array', () => {
const array = new Uint8Array(0);
expect(
() => uint8ArrayEqual(
array, /** @type {!Uint8Array} */ (/** @type {*} */ (null))))
.toThrow();
});
it('returns true for arrays with same values', () => {
const buffer1 = new Uint8Array([0, 1, 2, 3]).buffer;
const buffer2 = new Uint8Array([1, 2, 3, 4]).buffer;
const array1 = new Uint8Array(buffer1, 1, 3);
const array2 = new Uint8Array(buffer2, 0, 3);
expect(uint8ArrayEqual(array1, array2)).toBe(true);
});
it('returns false for arrays with different values', () => {
const array1 = new Uint8Array(4);
array1[0] = 1;
const array2 = new Uint8Array(4);
array2[0] = 2;
expect(uint8ArrayEqual(array1, array2)).toBe(false);
});
it('returns true same instance', () => {
const array1 = new Uint8Array(4);
array1[0] = 1;
expect(uint8ArrayEqual(array1, array1)).toBe(true);
});
});
describe('arrayBufferSlice', () => {
it('Returns a new instance.', () => {
const buffer1 = new ArrayBuffer(0);
const buffer2 = arrayBufferSlice(buffer1, 0);
expect(buffer2).not.toBe(buffer1);
expect(arrayBufferEqual(buffer1, buffer2)).toBe(true);
});
it('Copies data with positive start/end.', () => {
const buffer1 = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]).buffer;
expect(buffer1.byteLength).toEqual(10);
const buffer2 = arrayBufferSlice(buffer1, 2, 6);
expect(buffer2.byteLength).toEqual(4);
expect(buffer2).not.toBe(buffer1);
const expected = new Uint8Array([2, 3, 4, 5]).buffer;
expect(arrayBufferEqual(expected, buffer2)).toBe(true);
});
it('Copies all data without end.', () => {
const buffer1 = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]).buffer;
expect(buffer1.byteLength).toEqual(10);
const buffer2 = arrayBufferSlice(buffer1, 0);
expect(buffer2.byteLength).toEqual(10);
expect(arrayBufferEqual(buffer1, buffer2)).toBe(true);
});
if (goog.DEBUG) {
it('Fails with negative end.', () => {
const buffer1 = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]).buffer;
expect(() => void arrayBufferSlice(buffer1, 2, -1)).toThrow();
});
it('Fails with negative start.', () => {
const buffer1 = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]).buffer;
expect(() => void arrayBufferSlice(buffer1, 2, -1)).toThrow();
});
it('Fails when start > end.', () => {
const buffer1 = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]).buffer;
expect(() => void arrayBufferSlice(buffer1, 2, 1)).toThrow();
});
}
});
describe('cloneArrayBufferView', () => {
it('Returns a new instance.', () => {
const array1 = new Uint8Array(0);
const array2 = cloneArrayBufferView(new Uint8Array(array1));
expect(array2).not.toBe(array1);
expect(uint8ArrayEqual(array1, array2)).toBe(true);
});
it('Returns an array of the exact size.', () => {
const array1 =
new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]).subarray(2, 5);
expect(array1.length).toEqual(3);
expect(array1.buffer.byteLength).toEqual(10);
const array2 = cloneArrayBufferView(array1);
expect(array2.byteLength).toEqual(3);
expect(array2).toEqual(new Uint8Array([2, 3, 4]));
});
});
describe('hashUint8Array', () => {
it('returns same hashcode for empty Uint8Arrays', () => {
const array1 = new Uint8Array(0);
const array2 = new Uint8Array(0);
expect(hashUint8Array(array1)).toBe(hashUint8Array(array2));
});
it('returns same hashcode for Uint8Arrays with same values', () => {
const array1 = new Uint8Array(4);
array1[0] = 1;
const array2 = new Uint8Array(4);
array2[0] = 1;
expect(hashUint8Array(array1)).toBe(hashUint8Array(array2));
});
it('returns different hashcode for Uint8Arrays with different values', () => {
// This test might fail in the future if the hashing algorithm is updated
// and we end up with a collision here.
// We still need this test to make sure that we are not just returning
// the same number for all buffers.
const array1 = new Uint8Array(4);
array1[0] = 1;
const array2 = new Uint8Array(4);
array2[0] = 2;
expect(hashUint8Array(array1)).not.toBe(hashUint8Array(array2));
});
});

View File

@ -0,0 +1,68 @@
/**
* @fileoverview Test data for int32 encoding and decoding.
*/
goog.module('protobuf.binary.uint32TestPairs');
const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
/**
* An array of Pairs of float values and their bit representation.
* This is used to test encoding and decoding from/to the protobuf wire format.
* @return {!Array<{name: string, intValue:number, bufferDecoder:
* !BufferDecoder, error: (boolean|undefined),
* skip_reader: (boolean|undefined), skip_writer: (boolean|undefined)}>}
*/
function getUint32Pairs() {
const uint32Pairs = [
{
name: 'zero',
intValue: 0,
bufferDecoder: createBufferDecoder(0x00),
},
{
name: 'one ',
intValue: 1,
bufferDecoder: createBufferDecoder(0x01),
},
{
name: 'max signed int 2^31 - 1',
intValue: Math.pow(2, 31) - 1,
bufferDecoder: createBufferDecoder(0xFF, 0xFF, 0xFF, 0xFF, 0x07),
},
{
name: 'max unsigned int 2^32 - 1',
intValue: Math.pow(2, 32) - 1,
bufferDecoder: createBufferDecoder(0xFF, 0xFF, 0xFF, 0xFF, 0x0F),
},
{
name: 'negative one',
intValue: -1,
bufferDecoder: createBufferDecoder(0xFF, 0xFF, 0xFF, 0xFF, 0x0F),
skip_reader: true,
},
{
name: 'truncates more than 32 bits',
intValue: Math.pow(2, 32) - 1,
bufferDecoder: createBufferDecoder(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01),
skip_writer: true,
},
{
name: 'truncates more than 32 bits (bit 33 set)',
intValue: Math.pow(2, 28) - 1,
bufferDecoder: createBufferDecoder(0xFF, 0xFF, 0xFF, 0xFF, 0x10),
skip_writer: true,
},
{
name: 'errors out for 11 bytes',
intValue: Math.pow(2, 32) - 1,
bufferDecoder: createBufferDecoder(
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF),
error: true,
skip_writer: true,
},
];
return [...uint32Pairs];
}
exports = {getUint32Pairs};

View File

@ -0,0 +1,28 @@
/**
* @fileoverview Helper methods for Uint8Arrays.
*/
goog.module('protobuf.binary.uint8arrays');
/**
* Combines multiple bytes arrays (either Uint8Array or number array whose
* values are bytes) into a single Uint8Array.
* @param {!Array<!Uint8Array>|!Array<!Array<number>>} arrays
* @return {!Uint8Array}
*/
function concatenateByteArrays(arrays) {
let totalLength = 0;
for (const array of arrays) {
totalLength += array.length;
}
const result = new Uint8Array(totalLength);
let offset = 0;
for (const array of arrays) {
result.set(array, offset);
offset += array.length;
}
return result;
}
exports = {
concatenateByteArrays,
};

View File

@ -0,0 +1,47 @@
/**
* @fileoverview Tests for uint8arrays.js.
*/
goog.module('protobuf.binary.Uint8ArraysTest');
goog.setTestOnly();
const {concatenateByteArrays} = goog.require('protobuf.binary.uint8arrays');
describe('concatenateByteArrays does', () => {
it('concatenate empty array', () => {
const byteArrays = [];
expect(concatenateByteArrays(byteArrays)).toEqual(new Uint8Array(0));
});
it('concatenate Uint8Arrays', () => {
const byteArrays = [new Uint8Array([0x01]), new Uint8Array([0x02])];
expect(concatenateByteArrays(byteArrays)).toEqual(new Uint8Array([
0x01, 0x02
]));
});
it('concatenate array of bytes', () => {
const byteArrays = [[0x01], [0x02]];
expect(concatenateByteArrays(byteArrays)).toEqual(new Uint8Array([
0x01, 0x02
]));
});
it('concatenate array of non-bytes', () => {
// Note in unchecked mode we produce invalid output for invalid inputs.
// This test just documents our behavior in those cases.
// These values might change at any point and are not considered
// what the implementation should be doing here.
const byteArrays = [[40.0], [256]];
expect(concatenateByteArrays(byteArrays)).toEqual(new Uint8Array([
0x28, 0x00
]));
});
it('throw for null array', () => {
expect(
() => concatenateByteArrays(
/** @type {!Array<!Uint8Array>} */ (/** @type {*} */ (null))))
.toThrow();
});
});

View File

@ -0,0 +1,17 @@
goog.module('protobuf.binary.WireType');
/**
* Wire-format type codes, taken from proto2/public/wire_format_lite.h.
* @enum {number}
*/
const WireType = {
VARINT: 0,
FIXED64: 1,
DELIMITED: 2,
START_GROUP: 3,
END_GROUP: 4,
FIXED32: 5,
INVALID: 6
};
exports = WireType;

View File

@ -0,0 +1,743 @@
/**
* @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;

View File

@ -0,0 +1,927 @@
/**
* @fileoverview Tests for writer.js.
*/
goog.module('protobuf.binary.WriterTest');
goog.setTestOnly();
// Note to the reader:
// Since the writer behavior changes with the checking level some of the tests
// in this file have to know which checking level is enable to make correct
// assertions.
const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
const ByteString = goog.require('protobuf.ByteString');
const WireType = goog.require('protobuf.binary.WireType');
const Writer = goog.require('protobuf.binary.Writer');
const {CHECK_BOUNDS, CHECK_TYPE, MAX_FIELD_NUMBER} = goog.require('protobuf.internal.checks');
const {arrayBufferSlice} = goog.require('protobuf.binary.typedArrays');
const {getDoublePairs} = goog.require('protobuf.binary.doubleTestPairs');
const {getFixed32Pairs} = goog.require('protobuf.binary.fixed32TestPairs');
const {getFloatPairs} = goog.require('protobuf.binary.floatTestPairs');
const {getInt32Pairs} = goog.require('protobuf.binary.int32TestPairs');
const {getInt64Pairs} = goog.require('protobuf.binary.int64TestPairs');
const {getPackedBoolPairs} = goog.require('protobuf.binary.packedBoolTestPairs');
const {getPackedDoublePairs} = goog.require('protobuf.binary.packedDoubleTestPairs');
const {getPackedFixed32Pairs} = goog.require('protobuf.binary.packedFixed32TestPairs');
const {getPackedFloatPairs} = goog.require('protobuf.binary.packedFloatTestPairs');
const {getPackedInt32Pairs} = goog.require('protobuf.binary.packedInt32TestPairs');
const {getPackedInt64Pairs} = goog.require('protobuf.binary.packedInt64TestPairs');
const {getPackedSfixed32Pairs} = goog.require('protobuf.binary.packedSfixed32TestPairs');
const {getPackedSfixed64Pairs} = goog.require('protobuf.binary.packedSfixed64TestPairs');
const {getPackedSint32Pairs} = goog.require('protobuf.binary.packedSint32TestPairs');
const {getPackedSint64Pairs} = goog.require('protobuf.binary.packedSint64TestPairs');
const {getPackedUint32Pairs} = goog.require('protobuf.binary.packedUint32TestPairs');
const {getSfixed32Pairs} = goog.require('protobuf.binary.sfixed32TestPairs');
const {getSfixed64Pairs} = goog.require('protobuf.binary.sfixed64TestPairs');
const {getSint32Pairs} = goog.require('protobuf.binary.sint32TestPairs');
const {getSint64Pairs} = goog.require('protobuf.binary.sint64TestPairs');
const {getUint32Pairs} = goog.require('protobuf.binary.uint32TestPairs');
/**
* @param {...number} bytes
* @return {!ArrayBuffer}
*/
function createArrayBuffer(...bytes) {
return new Uint8Array(bytes).buffer;
}
/******************************************************************************
* OPTIONAL FUNCTIONS
******************************************************************************/
describe('Writer does', () => {
it('return an empty ArrayBuffer when nothing is encoded', () => {
const writer = new Writer();
expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer());
});
it('encode tag', () => {
const writer = new Writer();
writer.writeTag(1, WireType.VARINT);
expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer(0x08));
writer.writeTag(0x0FFFFFFF, WireType.VARINT);
expect(writer.getAndResetResultBuffer())
.toEqual(createArrayBuffer(0xF8, 0xFF, 0xFF, 0xFF, 0x7));
writer.writeTag(0x10000000, WireType.VARINT);
expect(writer.getAndResetResultBuffer())
.toEqual(createArrayBuffer(0x80, 0x80, 0x80, 0x80, 0x08));
writer.writeTag(0x1FFFFFFF, WireType.VARINT);
expect(writer.getAndResetResultBuffer())
.toEqual(createArrayBuffer(0xF8, 0xFF, 0xFF, 0xFF, 0x0F));
});
it('reset after calling getAndResetResultBuffer', () => {
const writer = new Writer();
writer.writeTag(1, WireType.VARINT);
writer.getAndResetResultBuffer();
expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer());
});
it('fail when field number is too large for writeTag', () => {
const writer = new Writer();
if (CHECK_TYPE) {
expect(() => writer.writeTag(MAX_FIELD_NUMBER + 1, WireType.VARINT))
.toThrowError('Field number is out of range: 536870912');
} else {
// Note in unchecked mode we produce invalid output for invalid inputs.
// This test just documents our behavior in those cases.
// These values might change at any point and are not considered
// what the implementation should be doing here.
writer.writeTag(MAX_FIELD_NUMBER + 1, WireType.VARINT);
expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer(0));
}
});
it('fail when field number is negative for writeTag', () => {
const writer = new Writer();
if (CHECK_TYPE) {
expect(() => writer.writeTag(-1, WireType.VARINT))
.toThrowError('Field number is out of range: -1');
} else {
// Note in unchecked mode we produce invalid output for invalid inputs.
// This test just documents our behavior in those cases.
// These values might change at any point and are not considered
// what the implementation should be doing here.
writer.writeTag(-1, WireType.VARINT);
expect(writer.getAndResetResultBuffer())
.toEqual(createArrayBuffer(0xF8, 0xFF, 0xFF, 0xFF, 0xF));
}
});
it('fail when wire type is invalid for writeTag', () => {
const writer = new Writer();
if (CHECK_TYPE) {
expect(() => writer.writeTag(1, /** @type {!WireType} */ (0x08)))
.toThrowError('Invalid wire type: 8');
} else {
// Note in unchecked mode we produce invalid output for invalid inputs.
// This test just documents our behavior in those cases.
// These values might change at any point and are not considered
// what the implementation should be doing here.
writer.writeTag(1, /** @type {!WireType} */ (0x08));
expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer(0x08));
}
});
it('encode singular boolean value', () => {
const writer = new Writer();
writer.writeBool(1, true);
expect(writer.getAndResetResultBuffer())
.toEqual(createArrayBuffer(0x08, 0x01));
});
it('encode length delimited', () => {
const writer = new Writer();
writer.writeDelimited(1, createArrayBuffer(0x01, 0x02));
expect(writer.getAndResetResultBuffer())
.toEqual(createArrayBuffer(0x0A, 0x02, 0x01, 0x02));
});
});
describe('Writer.writeBufferDecoder does', () => {
it('encode BufferDecoder containing a varint value', () => {
const writer = new Writer();
const expected = createArrayBuffer(
0x08, /* varint start= */ 0xFF, /* varint end= */ 0x01, 0x08, 0x01);
writer.writeBufferDecoder(
BufferDecoder.fromArrayBuffer(expected), 1, WireType.VARINT, 1);
const result = writer.getAndResetResultBuffer();
expect(result).toEqual(arrayBufferSlice(expected, 1, 3));
});
it('encode BufferDecoder containing a fixed64 value', () => {
const writer = new Writer();
const expected = createArrayBuffer(
0x09, /* fixed64 start= */ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
/* fixed64 end= */ 0x08, 0x08, 0x01);
writer.writeBufferDecoder(
BufferDecoder.fromArrayBuffer(expected), 1, WireType.FIXED64, 1);
const result = writer.getAndResetResultBuffer();
expect(result).toEqual(arrayBufferSlice(expected, 1, 9));
});
it('encode BufferDecoder containing a length delimited value', () => {
const writer = new Writer();
const expected = createArrayBuffer(
0xA, /* length= */ 0x03, /* data start= */ 0x01, 0x02,
/* data end= */ 0x03, 0x08, 0x01);
writer.writeBufferDecoder(
BufferDecoder.fromArrayBuffer(expected), 1, WireType.DELIMITED, 1);
const result = writer.getAndResetResultBuffer();
expect(result).toEqual(arrayBufferSlice(expected, 1, 5));
});
it('encode BufferDecoder containing a group', () => {
const writer = new Writer();
const expected = createArrayBuffer(
0xB, /* group start= */ 0x08, 0x01, /* nested group start= */ 0x0B,
/* nested group end= */ 0x0C, /* group end= */ 0x0C, 0x08, 0x01);
writer.writeBufferDecoder(
BufferDecoder.fromArrayBuffer(expected), 1, WireType.START_GROUP, 1);
const result = writer.getAndResetResultBuffer();
expect(result).toEqual(arrayBufferSlice(expected, 1, 6));
});
it('encode BufferDecoder containing a fixed32 value', () => {
const writer = new Writer();
const expected = createArrayBuffer(
0x09, /* fixed64 start= */ 0x01, 0x02, 0x03, /* fixed64 end= */ 0x04,
0x08, 0x01);
writer.writeBufferDecoder(
BufferDecoder.fromArrayBuffer(expected), 1, WireType.FIXED32, 1);
const result = writer.getAndResetResultBuffer();
expect(result).toEqual(arrayBufferSlice(expected, 1, 5));
});
it('fail when encoding out of bound data', () => {
const writer = new Writer();
const buffer = createArrayBuffer(0x4, 0x0, 0x1, 0x2, 0x3);
const subBuffer = arrayBufferSlice(buffer, 0, 2);
expect(
() => writer.writeBufferDecoder(
BufferDecoder.fromArrayBuffer(subBuffer), 0, WireType.DELIMITED, 1))
.toThrow();
});
});
describe('Writer.writeBytes does', () => {
let writer;
beforeEach(() => {
writer = new Writer();
});
it('encodes empty ByteString', () => {
writer.writeBytes(1, ByteString.EMPTY);
const buffer = writer.getAndResetResultBuffer();
expect(buffer.byteLength).toBe(2);
});
it('encodes empty array', () => {
writer.writeBytes(1, ByteString.fromArrayBuffer(new ArrayBuffer(0)));
expect(writer.getAndResetResultBuffer())
.toEqual(createArrayBuffer(
1 << 3 | 0x02, // tag (fieldnumber << 3 | (length delimited))
0, // length of the bytes
));
});
it('encodes ByteString', () => {
const array = createArrayBuffer(1, 2, 3);
writer.writeBytes(1, ByteString.fromArrayBuffer(array));
expect(writer.getAndResetResultBuffer())
.toEqual(createArrayBuffer(
1 << 3 | 0x02, // tag (fieldnumber << 3 | (length delimited))
3, // length of the bytes
1,
2,
3,
));
});
});
describe('Writer.writeDouble does', () => {
let writer;
beforeEach(() => {
writer = new Writer();
});
for (const pair of getDoublePairs()) {
it(`encode ${pair.name}`, () => {
writer.writeDouble(1, pair.doubleValue);
const buffer = new Uint8Array(writer.getAndResetResultBuffer());
expect(buffer.length).toBe(9);
// ensure we have a correct tag
expect(buffer[0]).toEqual(0x09);
// Encoded values are stored right after the tag
expect(buffer.subarray(1, 9))
.toEqual(pair.bufferDecoder.asUint8Array());
});
}
/**
* NaN may have different value in different browsers. Thus, we need to make
* the test lenient.
*/
it('encode NaN', () => {
writer.writeDouble(1, NaN);
const buffer = new Uint8Array(writer.getAndResetResultBuffer());
expect(buffer.length).toBe(9);
// ensure we have a correct tag
expect(buffer[0]).toEqual(0x09);
// Encoded values are stored right after the tag
const float64 = new DataView(buffer.buffer);
expect(float64.getFloat64(1, true)).toBeNaN();
});
});
describe('Writer.writeFixed32 does', () => {
let writer;
beforeEach(() => {
writer = new Writer();
});
for (const pair of getFixed32Pairs()) {
it(`encode ${pair.name}`, () => {
writer.writeFixed32(1, pair.intValue);
const buffer = new Uint8Array(writer.getAndResetResultBuffer());
expect(buffer.length).toBe(5);
// ensure we have a correct tag
expect(buffer[0]).toEqual(0x0D);
// Encoded values are stored right after the tag
expect(buffer.subarray(1, 5)).toEqual(pair.bufferDecoder.asUint8Array());
});
}
});
describe('Writer.writeFloat does', () => {
let writer;
beforeEach(() => {
writer = new Writer();
});
for (const pair of getFloatPairs()) {
it(`encode ${pair.name}`, () => {
writer.writeFloat(1, pair.floatValue);
const buffer = new Uint8Array(writer.getAndResetResultBuffer());
expect(buffer.length).toBe(5);
// ensure we have a correct tag
expect(buffer[0]).toEqual(0x0D);
// Encoded values are stored right after the tag
expect(buffer.subarray(1, 5)).toEqual(pair.bufferDecoder.asUint8Array());
});
}
/**
* NaN may have different value in different browsers. Thus, we need to make
* the test lenient.
*/
it('encode NaN', () => {
writer.writeFloat(1, NaN);
const buffer = new Uint8Array(writer.getAndResetResultBuffer());
expect(buffer.length).toBe(5);
// ensure we have a correct tag
expect(buffer[0]).toEqual(0x0D);
// Encoded values are stored right after the tag
const float32 = new DataView(buffer.buffer);
expect(float32.getFloat32(1, true)).toBeNaN();
});
});
describe('Writer.writeInt32 does', () => {
let writer;
beforeEach(() => {
writer = new Writer();
});
for (const pair of getInt32Pairs()) {
if (!pair.skip_writer) {
it(`encode ${pair.name}`, () => {
writer.writeInt32(1, pair.intValue);
const buffer = new Uint8Array(writer.getAndResetResultBuffer());
// ensure we have a correct tag
expect(buffer[0]).toEqual(0x08);
// Encoded values are stored right after the tag
expect(buffer.subarray(1, buffer.length))
.toEqual(pair.bufferDecoder.asUint8Array());
});
}
}
});
describe('Writer.writeSfixed32 does', () => {
let writer;
beforeEach(() => {
writer = new Writer();
});
it('encode empty array', () => {
writer.writePackedSfixed32(1, []);
expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer());
});
for (const pair of getSfixed32Pairs()) {
it(`encode ${pair.name}`, () => {
writer.writeSfixed32(1, pair.intValue);
const buffer = new Uint8Array(writer.getAndResetResultBuffer());
expect(buffer.length).toBe(5);
// ensure we have a correct tag
expect(buffer[0]).toEqual(0x0D);
// Encoded values are stored right after the tag
expect(buffer.subarray(1, 5)).toEqual(pair.bufferDecoder.asUint8Array());
});
}
});
describe('Writer.writeSfixed64 does', () => {
let writer;
beforeEach(() => {
writer = new Writer();
});
for (const pair of getSfixed64Pairs()) {
it(`encode ${pair.name}`, () => {
writer.writeSfixed64(1, pair.longValue);
const buffer = new Uint8Array(writer.getAndResetResultBuffer());
expect(buffer.length).toBe(9);
// ensure we have a correct tag
expect(buffer[0]).toEqual(0x09);
// Encoded values are stored right after the tag
expect(buffer.subarray(1, 9)).toEqual(pair.bufferDecoder.asUint8Array());
});
}
});
describe('Writer.writeSint32 does', () => {
let writer;
beforeEach(() => {
writer = new Writer();
});
for (const pair of getSint32Pairs()) {
if (!pair.skip_writer) {
it(`encode ${pair.name}`, () => {
writer.writeSint32(1, pair.intValue);
const buffer = new Uint8Array(writer.getAndResetResultBuffer());
// ensure we have a correct tag
expect(buffer[0]).toEqual(0x08);
// Encoded values are stored right after the tag
expect(buffer.subarray(1, buffer.length))
.toEqual(pair.bufferDecoder.asUint8Array());
});
}
}
});
describe('Writer.writeSint64 does', () => {
let writer;
beforeEach(() => {
writer = new Writer();
});
for (const pair of getSint64Pairs()) {
if (!pair.skip_writer) {
it(`encode ${pair.name}`, () => {
writer.writeSint64(1, pair.longValue);
const buffer = new Uint8Array(writer.getAndResetResultBuffer());
// ensure we have a correct tag
expect(buffer[0]).toEqual(0x08);
// Encoded values are stored right after the tag
expect(buffer.subarray(1, buffer.length))
.toEqual(pair.bufferDecoder.asUint8Array());
});
}
}
});
describe('Writer.writeInt64 does', () => {
let writer;
beforeEach(() => {
writer = new Writer();
});
for (const pair of getInt64Pairs()) {
if (!pair.skip_writer) {
it(`encode ${pair.name}`, () => {
writer.writeInt64(1, pair.longValue);
const buffer = new Uint8Array(writer.getAndResetResultBuffer());
// ensure we have a correct tag
expect(buffer[0]).toEqual(0x08);
// Encoded values are stored right after the tag
expect(buffer.subarray(1, buffer.length))
.toEqual(pair.bufferDecoder.asUint8Array());
});
}
}
});
describe('Writer.writeUint32 does', () => {
let writer;
beforeEach(() => {
writer = new Writer();
});
for (const pair of getUint32Pairs()) {
if (!pair.skip_writer) {
it(`encode ${pair.name}`, () => {
writer.writeUint32(1, pair.intValue);
const buffer = new Uint8Array(writer.getAndResetResultBuffer());
// ensure we have a correct tag
expect(buffer[0]).toEqual(0x08);
// Encoded values are stored right after the tag
expect(buffer.subarray(1, buffer.length))
.toEqual(pair.bufferDecoder.asUint8Array());
});
}
}
});
describe('Writer.writeString does', () => {
let writer;
beforeEach(() => {
writer = new Writer();
});
it('encode empty string', () => {
writer.writeString(1, '');
expect(writer.getAndResetResultBuffer())
.toEqual(createArrayBuffer(
1 << 3 | 0x02, // tag (fieldnumber << 3 | (length delimited))
0, // length of the string
));
});
it('encode simple string', () => {
writer.writeString(1, 'hello');
expect(writer.getAndResetResultBuffer())
.toEqual(createArrayBuffer(
1 << 3 | 0x02, // tag (fieldnumber << 3 | (length delimited))
5, // length of the string
'h'.charCodeAt(0),
'e'.charCodeAt(0),
'l'.charCodeAt(0),
'l'.charCodeAt(0),
'o'.charCodeAt(0),
));
});
it('throw for invalid fieldnumber', () => {
if (CHECK_BOUNDS) {
expect(() => writer.writeString(-1, 'a'))
.toThrowError('Field number is out of range: -1');
} else {
writer.writeString(-1, 'a');
expect(new Uint8Array(writer.getAndResetResultBuffer()))
.toEqual(new Uint8Array(createArrayBuffer(
-6, // invalid tag
0xff,
0xff,
0xff,
0x0f,
1, // string length
'a'.charCodeAt(0),
)));
}
});
it('throw for null string value', () => {
expect(
() => writer.writeString(
1, /** @type {string} */ (/** @type {*} */ (null))))
.toThrow();
});
});
/******************************************************************************
* REPEATED FUNCTIONS
******************************************************************************/
describe('Writer.writePackedBool does', () => {
let writer;
beforeEach(() => {
writer = new Writer();
});
it('encode empty array', () => {
writer.writePackedBool(1, []);
expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer());
});
for (const pair of getPackedBoolPairs()) {
if (!pair.skip_writer) {
it(`encode ${pair.name}`, () => {
writer.writePackedBool(1, pair.boolValues);
const buffer = new Uint8Array(writer.getAndResetResultBuffer());
// ensure we have a correct tag
expect(buffer[0]).toEqual(0x0A);
// Encoded values are stored right after the tag
expect(buffer.subarray(1, buffer.length))
.toEqual(pair.bufferDecoder.asUint8Array());
});
}
}
});
describe('Writer.writeRepeatedBool does', () => {
let writer;
beforeEach(() => {
writer = new Writer();
});
it('encode empty array', () => {
writer.writeRepeatedBool(1, []);
expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer());
});
it('encode repeated unpacked boolean values', () => {
const writer = new Writer();
writer.writeRepeatedBool(1, [true, false]);
expect(writer.getAndResetResultBuffer())
.toEqual(createArrayBuffer(
1 << 3 | 0x00, // tag (fieldnumber << 3 | (varint))
0x01, // value[0]
1 << 3 | 0x00, // tag (fieldnumber << 3 | (varint))
0x00, // value[1]
));
});
});
describe('Writer.writePackedDouble does', () => {
let writer;
beforeEach(() => {
writer = new Writer();
});
it('encode empty array', () => {
writer.writePackedDouble(1, []);
expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer());
});
for (const pair of getPackedDoublePairs()) {
if (!pair.skip_writer) {
it(`encode ${pair.name}`, () => {
writer.writePackedDouble(1, pair.doubleValues);
const buffer = new Uint8Array(writer.getAndResetResultBuffer());
// ensure we have a correct tag
expect(buffer[0]).toEqual(0x0A);
// Encoded values are stored right after the tag
expect(buffer.subarray(1, buffer.length))
.toEqual(pair.bufferDecoder.asUint8Array());
});
}
}
});
describe('Writer.writePackedFixed32 does', () => {
let writer;
beforeEach(() => {
writer = new Writer();
});
it('encode empty array', () => {
writer.writePackedFixed32(1, []);
expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer());
});
for (const pair of getPackedFixed32Pairs()) {
if (!pair.skip_writer) {
it(`encode ${pair.name}`, () => {
writer.writePackedFixed32(1, pair.fixed32Values);
const buffer = new Uint8Array(writer.getAndResetResultBuffer());
// ensure we have a correct tag
expect(buffer[0]).toEqual(0x0A);
// Encoded values are stored right after the tag
expect(buffer.subarray(1, buffer.length))
.toEqual(pair.bufferDecoder.asUint8Array());
});
}
}
});
describe('Writer.writePackedFloat does', () => {
let writer;
beforeEach(() => {
writer = new Writer();
});
it('encode empty array', () => {
writer.writePackedFloat(1, []);
expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer());
});
for (const pair of getPackedFloatPairs()) {
if (!pair.skip_writer) {
it(`encode ${pair.name}`, () => {
writer.writePackedFloat(1, pair.floatValues);
const buffer = new Uint8Array(writer.getAndResetResultBuffer());
// ensure we have a correct tag
expect(buffer[0]).toEqual(0x0A);
// Encoded values are stored right after the tag
expect(buffer.subarray(1, buffer.length))
.toEqual(pair.bufferDecoder.asUint8Array());
});
}
}
});
describe('Writer.writePackedInt32 does', () => {
let writer;
beforeEach(() => {
writer = new Writer();
});
it('encode empty array', () => {
writer.writePackedInt32(1, []);
expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer());
});
for (const pair of getPackedInt32Pairs()) {
if (!pair.skip_writer) {
it(`encode ${pair.name}`, () => {
writer.writePackedInt32(1, pair.int32Values);
const buffer = new Uint8Array(writer.getAndResetResultBuffer());
// ensure we have a correct tag
expect(buffer[0]).toEqual(0x0A);
// Encoded values are stored right after the tag
expect(buffer.subarray(1, buffer.length))
.toEqual(pair.bufferDecoder.asUint8Array());
});
}
}
});
describe('Writer.writePackedInt64 does', () => {
let writer;
beforeEach(() => {
writer = new Writer();
});
it('encode empty array', () => {
writer.writePackedInt64(1, []);
expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer());
});
for (const pair of getPackedInt64Pairs()) {
if (!pair.skip_writer) {
it(`encode ${pair.name}`, () => {
writer.writePackedInt64(1, pair.int64Values);
const buffer = new Uint8Array(writer.getAndResetResultBuffer());
// ensure we have a correct tag
expect(buffer[0]).toEqual(0x0A);
// Encoded values are stored right after the tag
expect(buffer.subarray(1, buffer.length))
.toEqual(pair.bufferDecoder.asUint8Array());
});
}
}
});
describe('Writer.writePackedSfixed32 does', () => {
let writer;
beforeEach(() => {
writer = new Writer();
});
it('encode empty array', () => {
writer.writePackedSfixed32(1, []);
expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer());
});
for (const pair of getPackedSfixed32Pairs()) {
if (!pair.skip_writer) {
it(`encode ${pair.name}`, () => {
writer.writePackedSfixed32(1, pair.sfixed32Values);
const buffer = new Uint8Array(writer.getAndResetResultBuffer());
// ensure we have a correct tag
expect(buffer[0]).toEqual(0x0A);
// Encoded values are stored right after the tag
expect(buffer.subarray(1, buffer.length))
.toEqual(pair.bufferDecoder.asUint8Array());
});
}
}
});
describe('Writer.writePackedSfixed64 does', () => {
let writer;
beforeEach(() => {
writer = new Writer();
});
it('encode empty array', () => {
writer.writePackedSfixed64(1, []);
expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer());
});
for (const pair of getPackedSfixed64Pairs()) {
if (!pair.skip_writer) {
it(`encode ${pair.name}`, () => {
writer.writePackedSfixed64(1, pair.sfixed64Values);
const buffer = new Uint8Array(writer.getAndResetResultBuffer());
// ensure we have a correct tag
expect(buffer[0]).toEqual(0x0A);
// Encoded values are stored right after the tag
expect(buffer.subarray(1, buffer.length))
.toEqual(pair.bufferDecoder.asUint8Array());
});
}
}
});
describe('Writer.writePackedSint32 does', () => {
let writer;
beforeEach(() => {
writer = new Writer();
});
it('encode empty array', () => {
writer.writePackedSint32(1, []);
expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer());
});
for (const pair of getPackedSint32Pairs()) {
if (!pair.skip_writer) {
it(`encode ${pair.name}`, () => {
writer.writePackedSint32(1, pair.sint32Values);
const buffer = new Uint8Array(writer.getAndResetResultBuffer());
// ensure we have a correct tag
expect(buffer[0]).toEqual(0x0A);
// Encoded values are stored right after the tag
expect(buffer.subarray(1, buffer.length))
.toEqual(pair.bufferDecoder.asUint8Array());
});
}
}
});
describe('Writer.writePackedSint64 does', () => {
let writer;
beforeEach(() => {
writer = new Writer();
});
it('encode empty array', () => {
writer.writePackedSint64(1, []);
expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer());
});
for (const pair of getPackedSint64Pairs()) {
if (!pair.skip_writer) {
it(`encode ${pair.name}`, () => {
writer.writePackedSint64(1, pair.sint64Values);
const buffer = new Uint8Array(writer.getAndResetResultBuffer());
// ensure we have a correct tag
expect(buffer[0]).toEqual(0x0A);
// Encoded values are stored right after the tag
expect(buffer.subarray(1, buffer.length))
.toEqual(pair.bufferDecoder.asUint8Array());
});
}
}
});
describe('Writer.writePackedUint32 does', () => {
let writer;
beforeEach(() => {
writer = new Writer();
});
it('encode empty array', () => {
writer.writePackedUint32(1, []);
expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer());
});
for (const pair of getPackedUint32Pairs()) {
if (!pair.skip_writer) {
it(`encode ${pair.name}`, () => {
writer.writePackedUint32(1, pair.uint32Values);
const buffer = new Uint8Array(writer.getAndResetResultBuffer());
// ensure we have a correct tag
expect(buffer[0]).toEqual(0x0A);
// Encoded values are stored right after the tag
expect(buffer.subarray(1, buffer.length))
.toEqual(pair.bufferDecoder.asUint8Array());
});
}
}
});
describe('Writer.writeRepeatedBytes does', () => {
let writer;
beforeEach(() => {
writer = new Writer();
});
it('encode empty array', () => {
writer.writeRepeatedBytes(1, []);
expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer());
});
it('encode single value', () => {
const value = createArrayBuffer(0x61);
writer.writeRepeatedBytes(1, [ByteString.fromArrayBuffer(value)]);
expect(writer.getAndResetResultBuffer())
.toEqual(createArrayBuffer(
0x0A,
0x01,
0x61, // a
));
});
it('encode multiple values', () => {
const value1 = createArrayBuffer(0x61);
const value2 = createArrayBuffer(0x62);
writer.writeRepeatedBytes(1, [
ByteString.fromArrayBuffer(value1),
ByteString.fromArrayBuffer(value2),
]);
expect(writer.getAndResetResultBuffer())
.toEqual(createArrayBuffer(
0x0A,
0x01,
0x61, // a
0x0A,
0x01,
0x62, // b
));
});
});
describe('Writer.writeRepeatedString does', () => {
let writer;
beforeEach(() => {
writer = new Writer();
});
it('encode empty array', () => {
writer.writeRepeatedString(1, []);
expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer());
});
it('encode single value', () => {
writer.writeRepeatedString(1, ['a']);
expect(writer.getAndResetResultBuffer())
.toEqual(createArrayBuffer(
0x0A,
0x01,
0x61, // a
));
});
it('encode multiple values', () => {
writer.writeRepeatedString(1, ['a', 'b']);
expect(writer.getAndResetResultBuffer())
.toEqual(createArrayBuffer(
0x0A,
0x01,
0x61, // a
0x0A,
0x01,
0x62, // b
));
});
});