parent
2d80a70048
commit
6b5fb807c0
42 changed files with 2700 additions and 351 deletions
@ -1,2 +1,3 @@ |
|||||||
Recommended.Proto3.JsonInput.BytesFieldBase64Url.JsonOutput |
Recommended.Proto3.JsonInput.BytesFieldBase64Url.JsonOutput |
||||||
Recommended.Proto3.JsonInput.BytesFieldBase64Url.ProtobufOutput |
Recommended.Proto3.JsonInput.BytesFieldBase64Url.ProtobufOutput |
||||||
|
Required.Proto2.JsonInput.StoresDefaultPrimitive.Validator |
||||||
|
@ -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; |
@ -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); |
||||||
|
}); |
||||||
|
}); |
@ -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); |
||||||
|
}); |
||||||
|
}); |
Loading…
Reference in new issue