parent
c781df3d21
commit
ed596ef68d
11 changed files with 1346 additions and 185 deletions
@ -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