Project import generated by Copybara

PiperOrigin-RevId: 310497816
pull/7479/head
Daniel Kurka 5 years ago committed by Copybara-Service
parent c781df3d21
commit ed596ef68d
  1. 0
      js/experimental/runtime/kernel/conformance/conformance_testee.js
  2. 0
      js/experimental/runtime/kernel/conformance/conformance_testee_runner_node.js
  3. 97
      js/experimental/runtime/kernel/indexer.js
  4. 33
      js/experimental/runtime/kernel/indexer_test.js
  5. 313
      js/experimental/runtime/kernel/kernel.js
  6. 380
      js/experimental/runtime/kernel/kernel_repeated_test.js
  7. 252
      js/experimental/runtime/kernel/kernel_test.js
  8. 144
      js/experimental/runtime/kernel/tag.js
  9. 221
      js/experimental/runtime/kernel/tag_test.js
  10. 79
      js/experimental/runtime/kernel/writer.js
  11. 12
      js/experimental/runtime/kernel/writer_test.js

@ -8,7 +8,8 @@ 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 {checkCriticalElementIndex, checkCriticalState} = goog.require('protobuf.internal.checks');
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.
@ -26,26 +27,6 @@ function addIndexEntry(storage, fieldNumber, wireType, startIndex) {
}
}
/**
* 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;
}
/**
* Creates an index of field locations in a given binary protobuf.
* @param {!BufferDecoder} bufferDecoder
@ -62,83 +43,13 @@ function buildIndex(bufferDecoder, pivot) {
const wireType = tagToWireType(tag);
const fieldNumber = tagToFieldNumber(tag);
checkCriticalState(fieldNumber > 0, `Invalid field number ${fieldNumber}`);
addIndexEntry(storage, fieldNumber, wireType, bufferDecoder.cursor());
checkCriticalState(
!skipField_(bufferDecoder, wireType, fieldNumber),
'Found unmatched stop group.');
skipField(bufferDecoder, wireType, fieldNumber);
}
return storage;
}
/**
* Skips over fields until the next field of the message.
* @param {!BufferDecoder} bufferDecoder
* @param {!WireType} wireType
* @param {number} fieldNumber
* @return {boolean} Whether the field we skipped over was a stop group.
* @private
*/
function skipField_(bufferDecoder, wireType, fieldNumber) {
switch (wireType) {
case WireType.VARINT:
checkCriticalElementIndex(
bufferDecoder.cursor(), bufferDecoder.endIndex());
bufferDecoder.skipVarint();
return false;
case WireType.FIXED64:
bufferDecoder.skip(8);
return false;
case WireType.DELIMITED:
checkCriticalElementIndex(
bufferDecoder.cursor(), bufferDecoder.endIndex());
const length = bufferDecoder.getUnsignedVarint32();
bufferDecoder.skip(length);
return false;
case WireType.START_GROUP:
checkCriticalState(
skipGroup_(bufferDecoder, fieldNumber), 'No end group found.');
return false;
case WireType.END_GROUP:
// Signal that we found a stop group to the caller
return true;
case WireType.FIXED32:
bufferDecoder.skip(4);
return false;
default:
throw new Error(`Invalid wire type: ${wireType}`);
}
}
/**
* Skips over fields until it finds the end of a given group.
* @param {!BufferDecoder} bufferDecoder
* @param {number} groupFieldNumber
* @return {boolean} Returns true if an end 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 (skipField_(bufferDecoder, wireType, fieldNumber)) {
checkCriticalState(
groupFieldNumber === fieldNumber,
`Expected stop group for fieldnumber ${groupFieldNumber} not found.`);
return true;
}
}
return false;
}
exports = {
buildIndex,
tagToWireType,
};

@ -72,12 +72,12 @@ describe('Indexer does', () => {
it('fail for invalid wire type (6)', () => {
expect(() => buildIndex(createBufferDecoder(0x0E, 0x01), PIVOT))
.toThrowError('Invalid wire type: 6');
.toThrowError('Unexpected wire type: 6');
});
it('fail for invalid wire type (7)', () => {
expect(() => buildIndex(createBufferDecoder(0x0F, 0x01), PIVOT))
.toThrowError('Invalid wire type: 7');
.toThrowError('Unexpected wire type: 7');
});
it('index varint', () => {
@ -269,33 +269,8 @@ describe('Indexer does', () => {
it('fail on unmatched stop group', () => {
const data = createBufferDecoder(0x0C, 0x01);
if (CHECK_CRITICAL_STATE) {
expect(() => buildIndex(data, PIVOT))
.toThrowError('Found unmatched stop group.');
} 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);
expect(getStorageSize(storage)).toBe(1);
const entryArray = storage.get(1).getIndexArray();
expect(entryArray).not.toBeUndefined();
expect(entryArray.length).toBe(1);
const entry = entryArray[0];
expect(Field.getWireType(entry)).toBe(WireType.END_GROUP);
expect(Field.getStartIndex(entry)).toBe(1);
const entryArray2 = storage.get(0).getIndexArray();
expect(entryArray2).not.toBeUndefined();
expect(entryArray2.length).toBe(1);
const entry2 = entryArray2[0];
expect(Field.getWireType(entry2)).toBe(WireType.FIXED64);
expect(Field.getStartIndex(entry2)).toBe(2);
}
expect(() => buildIndex(data, PIVOT))
.toThrowError('Unexpected wire type: 4');
});
it('fail for groups without matching stop group', () => {

@ -26,6 +26,7 @@ const reader = goog.require('protobuf.binary.reader');
const {CHECK_TYPE, checkCriticalElementIndex, checkCriticalState, checkCriticalType, checkCriticalTypeBool, checkCriticalTypeBoolArray, checkCriticalTypeByteString, checkCriticalTypeByteStringArray, checkCriticalTypeDouble, checkCriticalTypeDoubleArray, checkCriticalTypeFloat, checkCriticalTypeFloatIterable, checkCriticalTypeMessageArray, checkCriticalTypeSignedInt32, checkCriticalTypeSignedInt32Array, checkCriticalTypeSignedInt64, checkCriticalTypeSignedInt64Array, checkCriticalTypeString, checkCriticalTypeStringArray, checkCriticalTypeUnsignedInt32, checkCriticalTypeUnsignedInt32Array, checkDefAndNotNull, checkElementIndex, checkFieldNumber, checkFunctionExists, checkState, checkTypeDouble, checkTypeFloat, checkTypeSignedInt32, checkTypeSignedInt64, checkTypeUnsignedInt32} = goog.require('protobuf.internal.checks');
const {Field, IndexEntry} = goog.require('protobuf.binary.field');
const {buildIndex} = goog.require('protobuf.binary.indexer');
const {createTag, get32BitVarintLength, getTagLength} = goog.require('protobuf.binary.tag');
/**
@ -139,6 +140,28 @@ function readRepeatedNonPrimitive(indexArray, bufferDecoder, singularReadFunc) {
return result;
}
/**
* Converts all entries of the index array to the template type using the given
* read function and return an Array containing those converted values. This is
* used to implement parsing repeated non-primitive fields.
* @param {!Array<!IndexEntry>} indexArray
* @param {!BufferDecoder} bufferDecoder
* @param {number} fieldNumber
* @param {function(!Kernel):T} instanceCreator
* @param {number=} pivot
* @return {!Array<T>}
* @template T
*/
function readRepeatedGroup(
indexArray, bufferDecoder, fieldNumber, instanceCreator, pivot) {
const result = new Array(indexArray.length);
for (let i = 0; i < indexArray.length; i++) {
result[i] = doReadGroup(
bufferDecoder, indexArray[i], fieldNumber, instanceCreator, pivot);
}
return result;
}
/**
* Creates a new bytes array to contain all data of a submessage.
* When there are multiple entries, merge them together.
@ -193,6 +216,51 @@ function readMessage(indexArray, bufferDecoder, instanceCreator, pivot) {
return instanceCreator(accessor);
}
/**
* Merges all index entries of the index array using the given read function.
* This is used to implement parsing singular group fields.
* @param {!Array<!IndexEntry>} indexArray
* @param {!BufferDecoder} bufferDecoder
* @param {number} fieldNumber
* @param {function(!Kernel):T} instanceCreator
* @param {number=} pivot
* @return {T}
* @template T
*/
function readGroup(
indexArray, bufferDecoder, fieldNumber, instanceCreator, pivot) {
checkInstanceCreator(instanceCreator);
checkState(indexArray.length > 0);
return doReadGroup(
bufferDecoder, indexArray[indexArray.length - 1], fieldNumber,
instanceCreator, pivot);
}
/**
* Merges all index entries of the index array using the given read function.
* This is used to implement parsing singular message fields.
* @param {!BufferDecoder} bufferDecoder
* @param {!IndexEntry} indexEntry
* @param {number} fieldNumber
* @param {function(!Kernel):T} instanceCreator
* @param {number=} pivot
* @return {T}
* @template T
*/
function doReadGroup(
bufferDecoder, indexEntry, fieldNumber, instanceCreator, pivot) {
validateWireType(indexEntry, WireType.START_GROUP);
const fieldStartIndex = Field.getStartIndex(indexEntry);
const tag = createTag(WireType.START_GROUP, fieldNumber);
const groupTagLength = get32BitVarintLength(tag);
const groupLength = getTagLength(
bufferDecoder, fieldStartIndex, WireType.START_GROUP, fieldNumber);
const accessorBuffer = bufferDecoder.subBufferDecoder(
fieldStartIndex, groupLength - groupTagLength);
const kernel = Kernel.fromBufferDecoder_(accessorBuffer, pivot);
return instanceCreator(kernel);
}
/**
* @param {!Writer} writer
* @param {number} fieldNumber
@ -203,6 +271,18 @@ function writeMessage(writer, fieldNumber, value) {
fieldNumber, checkDefAndNotNull(value).internalGetKernel().serialize());
}
/**
* @param {!Writer} writer
* @param {number} fieldNumber
* @param {?InternalMessage} value
*/
function writeGroup(writer, fieldNumber, value) {
const kernel = checkDefAndNotNull(value).internalGetKernel();
writer.writeStartGroup(fieldNumber);
kernel.serializeToWriter(writer);
writer.writeEndGroup(fieldNumber);
}
/**
* Writes the array of Messages into the writer for the given field number.
* @param {!Writer} writer
@ -215,6 +295,18 @@ function writeRepeatedMessage(writer, fieldNumber, values) {
}
}
/**
* Writes the array of Messages into the writer for the given field number.
* @param {!Writer} writer
* @param {number} fieldNumber
* @param {!Array<!InternalMessage>} values
*/
function writeRepeatedGroup(writer, fieldNumber, values) {
for (const value of values) {
writeGroup(writer, fieldNumber, value);
}
}
/**
* Array.from has a weird type definition in google3/javascript/externs/es6.js
* and wants the mapping function accept strings.
@ -406,7 +498,8 @@ class Kernel {
writer.writeTag(fieldNumber, Field.getWireType(indexEntry));
writer.writeBufferDecoder(
checkDefAndNotNull(this.bufferDecoder_),
Field.getStartIndex(indexEntry), Field.getWireType(indexEntry));
Field.getStartIndex(indexEntry), Field.getWireType(indexEntry),
fieldNumber);
}
}
});
@ -690,6 +783,28 @@ class Kernel {
writeMessage);
}
/**
* Returns data as a mutable proto Message for the given field number.
* If no value has been set, return null.
* If hasFieldNumber(fieldNumber) == false before calling, it remains false.
*
* This method should not be used along with getMessage, since calling
* getMessageOrNull after getMessage will not register the encoder.
*
* @param {number} fieldNumber
* @param {function(!Kernel):T} instanceCreator
* @param {number=} pivot
* @return {?T}
* @template T
*/
getGroupOrNull(fieldNumber, instanceCreator, pivot = undefined) {
return this.getFieldWithDefault_(
fieldNumber, null,
(indexArray, bytes) =>
readGroup(indexArray, bytes, fieldNumber, instanceCreator, pivot),
writeGroup);
}
/**
* Returns data as a mutable proto Message for the given field number.
* If no value has been set previously, creates and attaches an instance.
@ -714,6 +829,30 @@ class Kernel {
return instance;
}
/**
* Returns data as a mutable proto Message for the given field number.
* If no value has been set previously, creates and attaches an instance.
* Postcondition: hasFieldNumber(fieldNumber) == true.
*
* This method should not be used along with getMessage, since calling
* getMessageAttach after getMessage will not register the encoder.
*
* @param {number} fieldNumber
* @param {function(!Kernel):T} instanceCreator
* @param {number=} pivot
* @return {T}
* @template T
*/
getGroupAttach(fieldNumber, instanceCreator, pivot = undefined) {
checkInstanceCreator(instanceCreator);
let instance = this.getGroupOrNull(fieldNumber, instanceCreator, pivot);
if (!instance) {
instance = instanceCreator(Kernel.createEmpty());
this.setField_(fieldNumber, instance, writeGroup);
}
return instance;
}
/**
* Returns data as a proto Message for the given field number.
* If no value has been set, return a default instance.
@ -744,6 +883,36 @@ class Kernel {
return message === null ? instanceCreator(Kernel.createEmpty()) : message;
}
/**
* Returns data as a proto Message for the given field number.
* If no value has been set, return a default instance.
* This default instance is guaranteed to be the same instance, unless this
* field is cleared.
* Does not register the encoder, so changes made to the returned
* sub-message will not be included when serializing the parent message.
* Use getMessageAttach() if the resulting sub-message should be mutable.
*
* This method should not be used along with getMessageOrNull or
* getMessageAttach, since these methods register the encoder.
*
* @param {number} fieldNumber
* @param {function(!Kernel):T} instanceCreator
* @param {number=} pivot
* @return {T}
* @template T
*/
getGroup(fieldNumber, instanceCreator, pivot = undefined) {
checkInstanceCreator(instanceCreator);
const message = this.getFieldWithDefault_(
fieldNumber, null,
(indexArray, bytes) =>
readGroup(indexArray, bytes, fieldNumber, instanceCreator, pivot));
// Returns an empty message as the default value if the field doesn't exist.
// We don't pass the default value to getFieldWithDefault_ to reduce object
// allocation.
return message === null ? instanceCreator(Kernel.createEmpty()) : message;
}
/**
* Returns the accessor for the given singular message, or returns null if
* it hasn't been set.
@ -1614,6 +1783,71 @@ class Kernel {
.length;
}
/**
* Returns an Array instance containing boolean values for the given field
* number.
* @param {number} fieldNumber
* @param {function(!Kernel):T} instanceCreator
* @param {number|undefined} pivot
* @return {!Array<T>}
* @template T
* @private
*/
getRepeatedGroupArray_(fieldNumber, instanceCreator, pivot) {
return this.getFieldWithDefault_(
fieldNumber, [],
(indexArray, bufferDecoder) => readRepeatedGroup(
indexArray, bufferDecoder, fieldNumber, instanceCreator, pivot),
writeRepeatedGroup);
}
/**
* Returns the element at index for the given field number as a group.
* @param {number} fieldNumber
* @param {function(!Kernel):T} instanceCreator
* @param {number} index
* @param {number=} pivot
* @return {T}
* @template T
*/
getRepeatedGroupElement(
fieldNumber, instanceCreator, index, pivot = undefined) {
const array =
this.getRepeatedGroupArray_(fieldNumber, instanceCreator, pivot);
checkCriticalElementIndex(index, array.length);
return array[index];
}
/**
* Returns an Iterable instance containing group values for the given field
* number.
* @param {number} fieldNumber
* @param {function(!Kernel):T} instanceCreator
* @param {number=} pivot
* @return {!Iterable<T>}
* @template T
*/
getRepeatedGroupIterable(fieldNumber, instanceCreator, pivot = undefined) {
// Don't split this statement unless needed. JS compiler thinks
// getRepeatedMessageArray_ might have side effects and doesn't inline the
// call in the compiled code. See cl/293894484 for details.
return new ArrayIterable(
this.getRepeatedGroupArray_(fieldNumber, instanceCreator, pivot));
}
/**
* Returns the size of the repeated field.
* @param {number} fieldNumber
* @param {function(!Kernel):T} instanceCreator
* @return {number}
* @param {number=} pivot
* @template T
*/
getRepeatedGroupSize(fieldNumber, instanceCreator, pivot = undefined) {
return this.getRepeatedGroupArray_(fieldNumber, instanceCreator, pivot)
.length;
}
/***************************************************************************
* OPTIONAL SETTER METHODS
***************************************************************************/
@ -1801,6 +2035,20 @@ class Kernel {
this.setInt64(fieldNumber, value);
}
/**
* Sets a proto Group to the field with the given field number.
* Instead of working with the Kernel inside of the message directly, we
* need the message instance to keep its reference equality for subsequent
* gettings.
* @param {number} fieldNumber
* @param {!InternalMessage} value
*/
setGroup(fieldNumber, value) {
checkCriticalType(
value !== null, 'Given value is not a message instance: null');
this.setField_(fieldNumber, value, writeGroup);
}
/**
* Sets a proto Message to the field with the given field number.
* Instead of working with the Kernel inside of the message directly, we
@ -3806,6 +4054,69 @@ class Kernel {
this.addRepeatedMessageIterable(
fieldNumber, [value], instanceCreator, pivot);
}
// Groups
/**
* Sets all message values into the field for the given field number.
* @param {number} fieldNumber
* @param {!Iterable<!InternalMessage>} values
*/
setRepeatedGroupIterable(fieldNumber, values) {
const /** !Array<!InternalMessage> */ array = Array.from(values);
checkCriticalTypeMessageArray(array);
this.setField_(fieldNumber, array, writeRepeatedGroup);
}
/**
* Adds all message values into the field for the given field number.
* @param {number} fieldNumber
* @param {!Iterable<!InternalMessage>} values
* @param {function(!Kernel):!InternalMessage} instanceCreator
* @param {number=} pivot
*/
addRepeatedGroupIterable(
fieldNumber, values, instanceCreator, pivot = undefined) {
const array = [
...this.getRepeatedGroupArray_(fieldNumber, instanceCreator, pivot),
...values,
];
checkCriticalTypeMessageArray(array);
// Needs to set it back with the new array.
this.setField_(fieldNumber, array, writeRepeatedGroup);
}
/**
* Sets a single message value into the field for the given field number at
* the given index.
* @param {number} fieldNumber
* @param {!InternalMessage} value
* @param {function(!Kernel):!InternalMessage} instanceCreator
* @param {number} index
* @param {number=} pivot
* @throws {!Error} if index is out of range when check mode is critical
*/
setRepeatedGroupElement(
fieldNumber, value, instanceCreator, index, pivot = undefined) {
checkInstanceCreator(instanceCreator);
checkCriticalType(
value !== null, 'Given value is not a message instance: null');
const array =
this.getRepeatedGroupArray_(fieldNumber, instanceCreator, pivot);
checkCriticalElementIndex(index, array.length);
array[index] = value;
}
/**
* Adds a single message value into the field for the given field number.
* @param {number} fieldNumber
* @param {!InternalMessage} value
* @param {function(!Kernel):!InternalMessage} instanceCreator
* @param {number=} pivot
*/
addRepeatedGroupElement(
fieldNumber, value, instanceCreator, pivot = undefined) {
this.addRepeatedGroupIterable(fieldNumber, [value], instanceCreator, pivot);
}
}
exports = Kernel;

@ -7425,3 +7425,383 @@ describe('Kernel for repeated message does', () => {
}
});
});
describe('Kernel for repeated groups does', () => {
it('return empty array for the empty input', () => {
const accessor = Kernel.createEmpty();
expectEqualToArray(
accessor.getRepeatedGroupIterable(1, TestMessage.instanceCreator), []);
});
it('ensure not the same instance returned for the empty input', () => {
const accessor = Kernel.createEmpty();
const list1 =
accessor.getRepeatedGroupIterable(1, TestMessage.instanceCreator);
const list2 =
accessor.getRepeatedGroupIterable(1, TestMessage.instanceCreator);
expect(list1).not.toBe(list2);
});
it('return size for the empty input', () => {
const accessor = Kernel.createEmpty();
expect(accessor.getRepeatedGroupSize(1, TestMessage.instanceCreator))
.toEqual(0);
});
it('return values from the input', () => {
const bytes1 = createArrayBuffer(0x08, 0x01);
const bytes2 = createArrayBuffer(0x08, 0x02);
const msg1 = new TestMessage(Kernel.fromArrayBuffer(bytes1));
const msg2 = new TestMessage(Kernel.fromArrayBuffer(bytes2));
const bytes =
createArrayBuffer(0x0B, 0x08, 0x01, 0x0C, 0x0B, 0x08, 0x02, 0x0C);
const accessor = Kernel.fromArrayBuffer(bytes);
expectEqualToMessageArray(
accessor.getRepeatedGroupIterable(1, TestMessage.instanceCreator),
[msg1, msg2]);
});
it('ensure not the same array instance returned', () => {
const bytes =
createArrayBuffer(0x0B, 0x08, 0x01, 0x0C, 0x0B, 0x08, 0x02, 0x0C);
const accessor = Kernel.fromArrayBuffer(bytes);
const list1 =
accessor.getRepeatedGroupIterable(1, TestMessage.instanceCreator);
const list2 =
accessor.getRepeatedGroupIterable(1, TestMessage.instanceCreator);
expect(list1).not.toBe(list2);
});
it('ensure the same array element returned for get iterable', () => {
const bytes =
createArrayBuffer(0x0B, 0x08, 0x01, 0x0C, 0x0B, 0x08, 0x02, 0x0C);
const accessor = Kernel.fromArrayBuffer(bytes);
const list1 =
accessor.getRepeatedGroupIterable(1, TestMessage.instanceCreator);
const list2 = accessor.getRepeatedGroupIterable(
1, TestMessage.instanceCreator, /* pivot= */ 0);
const array1 = Array.from(list1);
const array2 = Array.from(list2);
for (let i = 0; i < array1.length; i++) {
expect(array1[i]).toBe(array2[i]);
}
});
it('return accessors from the input', () => {
const bytes =
createArrayBuffer(0x0B, 0x08, 0x01, 0x0C, 0x0B, 0x08, 0x02, 0x0C);
const accessor = Kernel.fromArrayBuffer(bytes);
const [accessor1, accessor2] =
[...accessor.getRepeatedGroupIterable(1, TestMessage.instanceCreator)];
expect(accessor1.getInt32WithDefault(1)).toEqual(1);
expect(accessor2.getInt32WithDefault(1)).toEqual(2);
});
it('return accessors from the input when pivot is set', () => {
const bytes =
createArrayBuffer(0x0B, 0x08, 0x01, 0x0C, 0x0B, 0x08, 0x02, 0x0C);
const accessor = Kernel.fromArrayBuffer(bytes);
const [accessor1, accessor2] = [...accessor.getRepeatedGroupIterable(
1, TestMessage.instanceCreator, /* pivot= */ 0)];
expect(accessor1.getInt32WithDefault(1)).toEqual(1);
expect(accessor2.getInt32WithDefault(1)).toEqual(2);
});
it('return the repeated field element from the input', () => {
const bytes =
createArrayBuffer(0x0B, 0x08, 0x01, 0x0C, 0x0B, 0x08, 0x02, 0x0C);
const accessor = Kernel.fromArrayBuffer(bytes);
const msg1 = accessor.getRepeatedGroupElement(
/* fieldNumber= */ 1, TestMessage.instanceCreator,
/* index= */ 0);
const msg2 = accessor.getRepeatedGroupElement(
/* fieldNumber= */ 1, TestMessage.instanceCreator,
/* index= */ 1, /* pivot= */ 0);
expect(msg1.getInt32WithDefault(
/* fieldNumber= */ 1, /* default= */ 0))
.toEqual(1);
expect(msg2.getInt32WithDefault(
/* fieldNumber= */ 1, /* default= */ 0))
.toEqual(2);
});
it('ensure the same array element returned', () => {
const bytes =
createArrayBuffer(0x0B, 0x08, 0x01, 0x0C, 0x0B, 0x08, 0x02, 0x0C);
const accessor = Kernel.fromArrayBuffer(bytes);
const msg1 = accessor.getRepeatedGroupElement(
/* fieldNumber= */ 1, TestMessage.instanceCreator,
/* index= */ 0);
const msg2 = accessor.getRepeatedGroupElement(
/* fieldNumber= */ 1, TestMessage.instanceCreator,
/* index= */ 0);
expect(msg1).toBe(msg2);
});
it('return the size from the input', () => {
const bytes =
createArrayBuffer(0x0B, 0x08, 0x01, 0x0C, 0x0B, 0x08, 0x02, 0x0C);
const accessor = Kernel.fromArrayBuffer(bytes);
expect(accessor.getRepeatedGroupSize(1, TestMessage.instanceCreator))
.toEqual(2);
});
it('encode repeated message from the input', () => {
const bytes =
createArrayBuffer(0x0B, 0x08, 0x01, 0x0C, 0x0B, 0x08, 0x02, 0x0C);
const accessor = Kernel.fromArrayBuffer(bytes);
expect(accessor.serialize()).toEqual(bytes);
});
it('add a single value', () => {
const accessor = Kernel.createEmpty();
const bytes1 = createArrayBuffer(0x08, 0x01);
const msg1 = new TestMessage(Kernel.fromArrayBuffer(bytes1));
const bytes2 = createArrayBuffer(0x08, 0x02);
const msg2 = new TestMessage(Kernel.fromArrayBuffer(bytes2));
accessor.addRepeatedGroupElement(1, msg1, TestMessage.instanceCreator);
accessor.addRepeatedGroupElement(1, msg2, TestMessage.instanceCreator);
const result =
accessor.getRepeatedGroupIterable(1, TestMessage.instanceCreator);
expect(Array.from(result)).toEqual([msg1, msg2]);
});
it('add values', () => {
const accessor = Kernel.createEmpty();
const bytes1 = createArrayBuffer(0x08, 0x01);
const msg1 = new TestMessage(Kernel.fromArrayBuffer(bytes1));
const bytes2 = createArrayBuffer(0x08, 0x02);
const msg2 = new TestMessage(Kernel.fromArrayBuffer(bytes2));
accessor.addRepeatedGroupIterable(1, [msg1], TestMessage.instanceCreator);
accessor.addRepeatedGroupIterable(1, [msg2], TestMessage.instanceCreator);
const result =
accessor.getRepeatedGroupIterable(1, TestMessage.instanceCreator);
expect(Array.from(result)).toEqual([msg1, msg2]);
});
it('set a single value', () => {
const bytes = createArrayBuffer(0x0B, 0x08, 0x01, 0x0C);
const accessor = Kernel.fromArrayBuffer(bytes);
const subbytes = createArrayBuffer(0x08, 0x01);
const submsg = new TestMessage(Kernel.fromArrayBuffer(subbytes));
accessor.setRepeatedGroupElement(
/* fieldNumber= */ 1, submsg, TestMessage.instanceCreator,
/* index= */ 0);
const result =
accessor.getRepeatedGroupIterable(1, TestMessage.instanceCreator);
expect(Array.from(result)).toEqual([submsg]);
});
it('write submessage changes made via getRepeatedGroupElement', () => {
const bytes = createArrayBuffer(0x0B, 0x08, 0x05, 0x0C);
const expected = createArrayBuffer(0x0B, 0x08, 0x00, 0x0C);
const accessor = Kernel.fromArrayBuffer(bytes);
const submsg = accessor.getRepeatedGroupElement(
/* fieldNumber= */ 1, TestMessage.instanceCreator,
/* index= */ 0);
expect(submsg.getInt32WithDefault(1, 0)).toEqual(5);
submsg.setInt32(1, 0);
expect(accessor.serialize()).toEqual(expected);
});
it('set values', () => {
const accessor = Kernel.createEmpty();
const subbytes = createArrayBuffer(0x08, 0x01);
const submsg = new TestMessage(Kernel.fromArrayBuffer(subbytes));
accessor.setRepeatedGroupIterable(1, [submsg]);
const result =
accessor.getRepeatedGroupIterable(1, TestMessage.instanceCreator);
expect(Array.from(result)).toEqual([submsg]);
});
it('encode for adding single value', () => {
const accessor = Kernel.createEmpty();
const bytes1 = createArrayBuffer(0x08, 0x01);
const msg1 = new TestMessage(Kernel.fromArrayBuffer(bytes1));
const bytes2 = createArrayBuffer(0x08, 0x00);
const msg2 = new TestMessage(Kernel.fromArrayBuffer(bytes2));
const expected =
createArrayBuffer(0x0B, 0x08, 0x01, 0x0C, 0x0B, 0x08, 0x00, 0x0C);
accessor.addRepeatedGroupElement(1, msg1, TestMessage.instanceCreator);
accessor.addRepeatedGroupElement(1, msg2, TestMessage.instanceCreator);
const result = accessor.serialize();
expect(result).toEqual(expected);
});
it('encode for adding values', () => {
const accessor = Kernel.createEmpty();
const bytes1 = createArrayBuffer(0x08, 0x01);
const msg1 = new TestMessage(Kernel.fromArrayBuffer(bytes1));
const bytes2 = createArrayBuffer(0x08, 0x00);
const msg2 = new TestMessage(Kernel.fromArrayBuffer(bytes2));
const expected =
createArrayBuffer(0x0B, 0x08, 0x01, 0x0C, 0x0B, 0x08, 0x00, 0x0C);
accessor.addRepeatedGroupIterable(
1, [msg1, msg2], TestMessage.instanceCreator);
const result = accessor.serialize();
expect(result).toEqual(expected);
});
it('encode for setting single value', () => {
const bytes = createArrayBuffer(0x0B, 0x08, 0x00, 0x0C);
const accessor = Kernel.fromArrayBuffer(bytes);
const subbytes = createArrayBuffer(0x08, 0x01);
const submsg = new TestMessage(Kernel.fromArrayBuffer(subbytes));
const expected = createArrayBuffer(0x0B, 0x08, 0x01, 0x0C);
accessor.setRepeatedGroupElement(
/* fieldNumber= */ 1, submsg, TestMessage.instanceCreator,
/* index= */ 0);
const result = accessor.serialize();
expect(result).toEqual(expected);
});
it('encode for setting values', () => {
const accessor = Kernel.createEmpty();
const subbytes = createArrayBuffer(0x08, 0x01);
const submsg = new TestMessage(Kernel.fromArrayBuffer(subbytes));
const expected = createArrayBuffer(0x0B, 0x08, 0x01, 0x0C);
accessor.setRepeatedGroupIterable(1, [submsg]);
const result = accessor.serialize();
expect(result).toEqual(expected);
});
it('fail when getting groups value with other wire types', () => {
const accessor = Kernel.fromArrayBuffer(createArrayBuffer(
0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
if (CHECK_CRITICAL_STATE) {
expect(() => {
accessor.getRepeatedGroupIterable(1, TestMessage.instanceCreator);
}).toThrow();
}
});
it('fail when adding group values with wrong type value', () => {
const accessor = Kernel.createEmpty();
const fakeValue = /** @type {!TestMessage} */ (/** @type {*} */ (null));
if (CHECK_CRITICAL_STATE) {
expect(
() => accessor.addRepeatedGroupIterable(
1, [fakeValue], TestMessage.instanceCreator))
.toThrowError('Given value is not a message instance: null');
} 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.
accessor.addRepeatedGroupIterable(
1, [fakeValue], TestMessage.instanceCreator);
const list =
accessor.getRepeatedGroupIterable(1, TestMessage.instanceCreator);
expect(Array.from(list)).toEqual([null]);
}
});
it('fail when adding single group value with wrong type value', () => {
const accessor = Kernel.createEmpty();
const fakeValue = /** @type {!TestMessage} */ (/** @type {*} */ (null));
if (CHECK_CRITICAL_STATE) {
expect(
() => accessor.addRepeatedGroupElement(
1, fakeValue, TestMessage.instanceCreator))
.toThrowError('Given value is not a message instance: null');
} 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.
accessor.addRepeatedGroupElement(
1, fakeValue, TestMessage.instanceCreator);
const list =
accessor.getRepeatedGroupIterable(1, TestMessage.instanceCreator);
expect(Array.from(list)).toEqual([null]);
}
});
it('fail when setting message values with wrong type value', () => {
const accessor = Kernel.createEmpty();
const fakeValue = /** @type {!TestMessage} */ (/** @type {*} */ (null));
if (CHECK_CRITICAL_STATE) {
expect(() => accessor.setRepeatedGroupIterable(1, [fakeValue]))
.toThrowError('Given value is not a message instance: null');
} 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.
accessor.setRepeatedGroupIterable(1, [fakeValue]);
const list =
accessor.getRepeatedGroupIterable(1, TestMessage.instanceCreator);
expect(Array.from(list)).toEqual([null]);
}
});
it('fail when setting single value with wrong type value', () => {
const accessor =
Kernel.fromArrayBuffer(createArrayBuffer(0x0B, 0x08, 0x00, 0x0C));
const fakeValue = /** @type {!TestMessage} */ (/** @type {*} */ (null));
if (CHECK_CRITICAL_STATE) {
expect(
() => accessor.setRepeatedGroupElement(
/* fieldNumber= */ 1, fakeValue, TestMessage.instanceCreator,
/* index= */ 0))
.toThrowError('Given value is not a message instance: null');
} 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.
accessor.setRepeatedGroupElement(
/* fieldNumber= */ 1, fakeValue, TestMessage.instanceCreator,
/* index= */ 0);
const list =
accessor.getRepeatedGroupIterable(1, TestMessage.instanceCreator);
expect(Array.from(list).length).toEqual(1);
}
});
it('fail when setting single value with out-of-bound index', () => {
const accessor =
Kernel.fromArrayBuffer(createArrayBuffer(0x0B, 0x08, 0x00, 0x0C));
const msg1 =
accessor.getRepeatedGroupElement(1, TestMessage.instanceCreator, 0);
const bytes2 = createArrayBuffer(0x08, 0x01);
const msg2 = new TestMessage(Kernel.fromArrayBuffer(bytes2));
if (CHECK_CRITICAL_STATE) {
expect(
() => accessor.setRepeatedGroupElement(
/* fieldNumber= */ 1, msg2, TestMessage.instanceCreator,
/* index= */ 1))
.toThrowError('Index out of bounds: index: 1 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.
accessor.setRepeatedGroupElement(
/* fieldNumber= */ 1, msg2, TestMessage.instanceCreator,
/* index= */ 1);
expectEqualToArray(
accessor.getRepeatedGroupIterable(1, TestMessage.instanceCreator),
[msg1, msg2]);
}
});
});

@ -2075,3 +2075,255 @@ describe('Double access', () => {
}
});
});
describe('Kernel for singular group does', () => {
it('return group from the input', () => {
const bytes = createArrayBuffer(0x0B, 0x08, 0x01, 0x0C);
const accessor = Kernel.fromArrayBuffer(bytes);
const msg = accessor.getGroupOrNull(1, TestMessage.instanceCreator);
expect(msg.getBoolWithDefault(1, false)).toBe(true);
});
it('return group from the input when pivot is set', () => {
const bytes = createArrayBuffer(0x0B, 0x08, 0x01, 0x0C);
const accessor = Kernel.fromArrayBuffer(bytes);
const msg = accessor.getGroupOrNull(1, TestMessage.instanceCreator, 0);
expect(msg.getBoolWithDefault(1, false)).toBe(true);
});
it('encode group from the input', () => {
const bytes = createArrayBuffer(0x0B, 0x08, 0x01, 0x0C);
const accessor = Kernel.fromArrayBuffer(bytes);
expect(accessor.serialize()).toEqual(bytes);
});
it('encode group from the input after read', () => {
const bytes = createArrayBuffer(0x0B, 0x08, 0x01, 0x0C);
const accessor = Kernel.fromArrayBuffer(bytes);
accessor.getGroupOrNull(1, TestMessage.instanceCreator);
expect(accessor.serialize()).toEqual(bytes);
});
it('return last group from multiple inputs', () => {
const bytes =
createArrayBuffer(0x0B, 0x08, 0x00, 0x0C, 0x0B, 0x08, 0x01, 0x0C);
const accessor = Kernel.fromArrayBuffer(bytes);
const msg = accessor.getGroupOrNull(1, TestMessage.instanceCreator);
expect(msg.getBoolWithDefault(1, false)).toBe(true);
});
it('removes duplicated group when serializing', () => {
const bytes =
createArrayBuffer(0x0B, 0x08, 0x00, 0x0C, 0x0B, 0x08, 0x01, 0x0C);
const accessor = Kernel.fromArrayBuffer(bytes);
accessor.getGroupOrNull(1, TestMessage.instanceCreator);
expect(accessor.serialize())
.toEqual(createArrayBuffer(0x0B, 0x08, 0x01, 0x0C));
});
it('encode group from multiple inputs', () => {
const bytes =
createArrayBuffer(0x0B, 0x08, 0x00, 0x0C, 0x0B, 0x08, 0x01, 0x0C);
const accessor = Kernel.fromArrayBuffer(bytes);
expect(accessor.serialize()).toEqual(bytes);
});
it('encode group after read', () => {
const bytes =
createArrayBuffer(0x0B, 0x08, 0x00, 0x0C, 0x0B, 0x08, 0x01, 0x0C);
const expected = createArrayBuffer(0x0B, 0x08, 0x01, 0x0C);
const accessor = Kernel.fromArrayBuffer(bytes);
accessor.getGroupOrNull(1, TestMessage.instanceCreator);
expect(accessor.serialize()).toEqual(expected);
});
it('return group from setter', () => {
const bytes = createArrayBuffer(0x08, 0x01);
const accessor = Kernel.fromArrayBuffer(new ArrayBuffer(0));
const subaccessor = Kernel.fromArrayBuffer(bytes);
const submsg1 = new TestMessage(subaccessor);
accessor.setGroup(1, submsg1);
const submsg2 = accessor.getGroup(1, TestMessage.instanceCreator);
expect(submsg1).toBe(submsg2);
});
it('encode group from setter', () => {
const accessor = Kernel.fromArrayBuffer(new ArrayBuffer(0));
const subaccessor = Kernel.fromArrayBuffer(createArrayBuffer(0x08, 0x01));
const submsg = new TestMessage(subaccessor);
accessor.setGroup(1, submsg);
const expected = createArrayBuffer(0x0B, 0x08, 0x01, 0x0C);
expect(accessor.serialize()).toEqual(expected);
});
it('leave hasFieldNumber unchanged after getGroupOrNull', () => {
const accessor = Kernel.createEmpty();
expect(accessor.hasFieldNumber(1)).toBe(false);
expect(accessor.getGroupOrNull(1, TestMessage.instanceCreator)).toBe(null);
expect(accessor.hasFieldNumber(1)).toBe(false);
});
it('serialize changes to subgroups made with getGroupsOrNull', () => {
const intTwoBytes = createArrayBuffer(0x0B, 0x08, 0x02, 0x0C);
const accessor = Kernel.fromArrayBuffer(intTwoBytes);
const mutableSubMessage =
accessor.getGroupOrNull(1, TestMessage.instanceCreator);
mutableSubMessage.setInt32(1, 10);
const intTenBytes = createArrayBuffer(0x0B, 0x08, 0x0A, 0x0C);
expect(accessor.serialize()).toEqual(intTenBytes);
});
it('serialize additions to subgroups made with getGroupOrNull', () => {
const intTwoBytes = createArrayBuffer(0x0B, 0x08, 0x02, 0x0C);
const accessor = Kernel.fromArrayBuffer(intTwoBytes);
const mutableSubMessage =
accessor.getGroupOrNull(1, TestMessage.instanceCreator);
mutableSubMessage.setInt32(2, 3);
// Sub group contains the original field, plus the new one.
expect(accessor.serialize())
.toEqual(createArrayBuffer(0x0B, 0x08, 0x02, 0x10, 0x03, 0x0C));
});
it('fail with getGroupOrNull if immutable group exist in cache', () => {
const intTwoBytes = createArrayBuffer(0x0B, 0x08, 0x02, 0x0C);
const accessor = Kernel.fromArrayBuffer(intTwoBytes);
const readOnly = accessor.getGroup(1, TestMessage.instanceCreator);
if (CHECK_TYPE) {
expect(() => accessor.getGroupOrNull(1, TestMessage.instanceCreator))
.toThrow();
} else {
const mutableSubGropu =
accessor.getGroupOrNull(1, TestMessage.instanceCreator);
// The instance returned by getGroupOrNull is the exact same instance.
expect(mutableSubGropu).toBe(readOnly);
// Serializing the subgroup does not write the changes
mutableSubGropu.setInt32(1, 0);
expect(accessor.serialize()).toEqual(intTwoBytes);
}
});
it('change hasFieldNumber after getGroupAttach', () => {
const accessor = Kernel.createEmpty();
expect(accessor.hasFieldNumber(1)).toBe(false);
expect(accessor.getGroupAttach(1, TestMessage.instanceCreator))
.not.toBe(null);
expect(accessor.hasFieldNumber(1)).toBe(true);
});
it('change hasFieldNumber after getGroupAttach when pivot is set', () => {
const accessor = Kernel.createEmpty();
expect(accessor.hasFieldNumber(1)).toBe(false);
expect(
accessor.getGroupAttach(1, TestMessage.instanceCreator, /* pivot= */ 1))
.not.toBe(null);
expect(accessor.hasFieldNumber(1)).toBe(true);
});
it('serialize subgroups made with getGroupAttach', () => {
const accessor = Kernel.createEmpty();
const mutableSubGroup =
accessor.getGroupAttach(1, TestMessage.instanceCreator);
mutableSubGroup.setInt32(1, 10);
const intTenBytes = createArrayBuffer(0x0B, 0x08, 0x0A, 0x0C);
expect(accessor.serialize()).toEqual(intTenBytes);
});
it('serialize additions to subgroups using getMessageAttach', () => {
const intTwoBytes = createArrayBuffer(0x0B, 0x08, 0x02, 0x0C);
const accessor = Kernel.fromArrayBuffer(intTwoBytes);
const mutableSubGroup =
accessor.getGroupAttach(1, TestMessage.instanceCreator);
mutableSubGroup.setInt32(2, 3);
// Sub message contains the original field, plus the new one.
expect(accessor.serialize())
.toEqual(createArrayBuffer(0x0B, 0x08, 0x02, 0x10, 0x03, 0x0C));
});
it('fail with getGroupAttach if immutable message exist in cache', () => {
const intTwoBytes = createArrayBuffer(0x0B, 0x08, 0x02, 0x0C);
const accessor = Kernel.fromArrayBuffer(intTwoBytes);
const readOnly = accessor.getGroup(1, TestMessage.instanceCreator);
if (CHECK_TYPE) {
expect(() => accessor.getGroupAttach(1, TestMessage.instanceCreator))
.toThrow();
} else {
const mutableSubGroup =
accessor.getGroupAttach(1, TestMessage.instanceCreator);
// The instance returned by getMessageOrNull is the exact same instance.
expect(mutableSubGroup).toBe(readOnly);
// Serializing the submessage does not write the changes
mutableSubGroup.setInt32(1, 0);
expect(accessor.serialize()).toEqual(intTwoBytes);
}
});
it('read default group return empty group with getGroup', () => {
const bytes = new ArrayBuffer(0);
const accessor = Kernel.fromArrayBuffer(bytes);
expect(accessor.getGroup(1, TestMessage.instanceCreator)).toBeTruthy();
expect(accessor.getGroup(1, TestMessage.instanceCreator).serialize())
.toEqual(bytes);
});
it('read default group return null with getGroupOrNull', () => {
const bytes = new ArrayBuffer(0);
const accessor = Kernel.fromArrayBuffer(bytes);
expect(accessor.getGroupOrNull(1, TestMessage.instanceCreator)).toBe(null);
});
it('read group preserve reference equality', () => {
const bytes = createArrayBuffer(0x0B, 0x08, 0x02, 0x0C);
const accessor = Kernel.fromArrayBuffer(bytes);
const msg1 = accessor.getGroupOrNull(1, TestMessage.instanceCreator);
const msg2 = accessor.getGroupOrNull(1, TestMessage.instanceCreator);
const msg3 = accessor.getGroupAttach(1, TestMessage.instanceCreator);
expect(msg1).toBe(msg2);
expect(msg1).toBe(msg3);
});
it('fail when getting group with null instance constructor', () => {
const accessor =
Kernel.fromArrayBuffer(createArrayBuffer(0x0A, 0x02, 0x08, 0x01));
const nullMessage = /** @type {function(!Kernel):!TestMessage} */
(/** @type {*} */ (null));
expect(() => accessor.getGroupOrNull(1, nullMessage)).toThrow();
});
it('fail when setting group value with null value', () => {
const accessor = Kernel.fromArrayBuffer(new ArrayBuffer(0));
const fakeMessage = /** @type {!TestMessage} */ (/** @type {*} */ (null));
if (CHECK_CRITICAL_TYPE) {
expect(() => accessor.setGroup(1, fakeMessage))
.toThrowError('Given value is not a message instance: null');
} 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.
accessor.setMessage(1, fakeMessage);
expect(accessor.getGroupOrNull(
/* fieldNumber= */ 1, TestMessage.instanceCreator))
.toBeNull();
}
});
it('reads group in a longer buffer', () => {
const bytes = createArrayBuffer(
0x12, 0x20, // 32 length delimited
0x00, // random values for padding start
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, // random values for padding end
0x0B, // Group tag
0x08, 0x02, 0x0C);
const accessor = Kernel.fromArrayBuffer(bytes);
const msg1 = accessor.getGroupOrNull(1, TestMessage.instanceCreator);
const msg2 = accessor.getGroupOrNull(1, TestMessage.instanceCreator);
expect(msg1).toBe(msg2);
});
});

@ -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,
};

@ -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 outter
0x10, 0x01, // field: 2, value: 1
0x0B, // start inner group
0x10, 0x01, // payload inner group
0x0C, // stop inner group
0x0C // end outter
);
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);
});
});

@ -10,6 +10,7 @@ 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');
/**
@ -92,8 +93,8 @@ class Writer {
writeTag(fieldNumber, wireType) {
checkFieldNumber(fieldNumber);
checkWireType(wireType);
const tag = fieldNumber << 3 | wireType;
this.writeUnsignedVarint32_(tag >>> 0);
const tag = createTag(wireType, fieldNumber);
this.writeUnsignedVarint32_(tag);
}
/**
@ -299,6 +300,22 @@ class Writer {
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
@ -430,67 +447,17 @@ class Writer {
* @param {!BufferDecoder} bufferDecoder
* @param {number} start
* @param {!WireType} wireType
* @param {number} fieldNumber
* @package
*/
writeBufferDecoder(bufferDecoder, start, wireType) {
writeBufferDecoder(bufferDecoder, start, wireType, fieldNumber) {
this.closeAndStartNewBuffer_();
const dataLength = this.getLength_(bufferDecoder, start, wireType);
const dataLength =
getTagLength(bufferDecoder, start, wireType, fieldNumber);
this.blocks_.push(
bufferDecoder.subBufferDecoder(start, dataLength).asUint8Array());
}
/**
* Returns the length of the data to serialize. Returns -1 when a STOP GROUP
* is found.
* @param {!BufferDecoder} bufferDecoder
* @param {number} start
* @param {!WireType} wireType
* @return {number}
* @private
*/
getLength_(bufferDecoder, start, wireType) {
switch (wireType) {
case WireType.VARINT:
bufferDecoder.setCursor(start);
bufferDecoder.skipVarint();
return bufferDecoder.cursor() - start;
case WireType.FIXED64:
return 8;
case WireType.DELIMITED:
const dataLength = bufferDecoder.getUnsignedVarint32At(start);
return dataLength + bufferDecoder.cursor() - start;
case WireType.START_GROUP:
return this.getGroupLength_(bufferDecoder, start);
case WireType.FIXED32:
return 4;
default:
throw new Error(`Invalid wire type: ${wireType}`);
}
}
/**
* Skips over fields until it finds the end of a given group.
* @param {!BufferDecoder} bufferDecoder
* @param {number} start
* @return {number}
* @private
*/
getGroupLength_(bufferDecoder, start) {
// On a start group we need to keep skipping fields until we find a
// corresponding stop group
let cursor = start;
while (cursor < bufferDecoder.endIndex()) {
const tag = bufferDecoder.getUnsignedVarint32At(cursor);
const wireType = /** @type {!WireType} */ (tag & 0x07);
if (wireType === WireType.END_GROUP) {
return bufferDecoder.cursor() - start;
}
cursor = bufferDecoder.cursor() +
this.getLength_(bufferDecoder, bufferDecoder.cursor(), wireType);
}
throw new Error('No end group found');
}
/**
* Write the whole bytes as a length delimited field.
* @param {number} fieldNumber

@ -148,7 +148,7 @@ describe('Writer.writeBufferDecoder does', () => {
const expected = createArrayBuffer(
0x08, /* varint start= */ 0xFF, /* varint end= */ 0x01, 0x08, 0x01);
writer.writeBufferDecoder(
BufferDecoder.fromArrayBuffer(expected), 1, WireType.VARINT);
BufferDecoder.fromArrayBuffer(expected), 1, WireType.VARINT, 1);
const result = writer.getAndResetResultBuffer();
expect(result).toEqual(arrayBufferSlice(expected, 1, 3));
});
@ -159,7 +159,7 @@ describe('Writer.writeBufferDecoder does', () => {
0x09, /* fixed64 start= */ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
/* fixed64 end= */ 0x08, 0x08, 0x01);
writer.writeBufferDecoder(
BufferDecoder.fromArrayBuffer(expected), 1, WireType.FIXED64);
BufferDecoder.fromArrayBuffer(expected), 1, WireType.FIXED64, 1);
const result = writer.getAndResetResultBuffer();
expect(result).toEqual(arrayBufferSlice(expected, 1, 9));
});
@ -170,7 +170,7 @@ describe('Writer.writeBufferDecoder does', () => {
0xA, /* length= */ 0x03, /* data start= */ 0x01, 0x02,
/* data end= */ 0x03, 0x08, 0x01);
writer.writeBufferDecoder(
BufferDecoder.fromArrayBuffer(expected), 1, WireType.DELIMITED);
BufferDecoder.fromArrayBuffer(expected), 1, WireType.DELIMITED, 1);
const result = writer.getAndResetResultBuffer();
expect(result).toEqual(arrayBufferSlice(expected, 1, 5));
});
@ -181,7 +181,7 @@ describe('Writer.writeBufferDecoder does', () => {
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);
BufferDecoder.fromArrayBuffer(expected), 1, WireType.START_GROUP, 1);
const result = writer.getAndResetResultBuffer();
expect(result).toEqual(arrayBufferSlice(expected, 1, 6));
});
@ -192,7 +192,7 @@ describe('Writer.writeBufferDecoder does', () => {
0x09, /* fixed64 start= */ 0x01, 0x02, 0x03, /* fixed64 end= */ 0x04,
0x08, 0x01);
writer.writeBufferDecoder(
BufferDecoder.fromArrayBuffer(expected), 1, WireType.FIXED32);
BufferDecoder.fromArrayBuffer(expected), 1, WireType.FIXED32, 1);
const result = writer.getAndResetResultBuffer();
expect(result).toEqual(arrayBufferSlice(expected, 1, 5));
});
@ -203,7 +203,7 @@ describe('Writer.writeBufferDecoder does', () => {
const subBuffer = arrayBufferSlice(buffer, 0, 2);
expect(
() => writer.writeBufferDecoder(
BufferDecoder.fromArrayBuffer(subBuffer), 0, WireType.DELIMITED))
BufferDecoder.fromArrayBuffer(subBuffer), 0, WireType.DELIMITED, 1))
.toThrow();
});
});

Loading…
Cancel
Save