Commit f2099695 authored by Ken Rockot's avatar Ken Rockot Committed by Commit Bot

[mojo-bindings] Fix JS lite serialization

Fixes mojom JS lite serialization in cases where fields are
intentionally packed out of order.

This actually simplifies the serialization and deserialization logic
significantly by leveraging precomputed byte/bit offset values from
the bindings generator and preallocating the entire message buffer
prior to serialization.

It also is now correct instead of incorrect, which is nice.

Bug: 904861
Change-Id: If08c301303004d164f4a0afcc3085dac86ca3089
Reviewed-on: https://chromium-review.googlesource.com/c/1335269Reviewed-by: default avatarcalamity <calamity@chromium.org>
Commit-Queue: Ken Rockot <rockot@google.com>
Cr-Commit-Position: refs/heads/master@{#607980}
parent aed4a114
......@@ -255,7 +255,8 @@ mojo.internal.InterfaceProxyBase = class {
this.pendingResponses_.delete(header.requestId);
if (!pendingResponse)
return this.onError_('Received unexpected response message');
const decoder = new mojo.internal.Decoder(data, handles, header.headerSize);
const decoder = new mojo.internal.Decoder(
new DataView(buffer, header.headerSize), handles);
const responseValue =
decoder.decodeStructInline(pendingResponse.responseStruct.$.structSpec);
if (!responseValue)
......@@ -353,7 +354,7 @@ mojo.internal.InterfaceCallbackTarget = class {
}
/**
* @param {...*}
* @param {...*} arguments
* @private
*/
dispatch_() {
......@@ -362,7 +363,7 @@ mojo.internal.InterfaceCallbackTarget = class {
}
/**
* @param {...*}
* @param {...*} arguments
* @return {?Object}
* @private
*/
......@@ -461,7 +462,8 @@ mojo.internal.InterfaceTarget = class {
const handler = this.messageHandlers_.get(header.ordinal);
if (!handler)
throw new Error('Received unknown message');
const decoder = new mojo.internal.Decoder(data, handles, header.headerSize);
const decoder = new mojo.internal.Decoder(
new DataView(buffer, header.headerSize), handles);
const request =
decoder.decodeStructInline(handler.paramStruct.$.structSpec);
if (!request)
......@@ -699,6 +701,38 @@ mojo.internal.getInt64 = function(dataView, byteOffset) {
return value;
};
/**
* @param {!mojo.internal.StructSpec} structSpec
* @param {*} value
* @return {number}
*/
mojo.internal.computeTotalStructSize = function(structSpec, value) {
let size = mojo.internal.kStructHeaderSize + structSpec.packedSize;
for (const field of structSpec.fields) {
const fieldValue = value[field.name];
if (field.type.$.computePayloadSize &&
!mojo.internal.isNullOrUndefined(fieldValue)) {
size +=
mojo.internal.align(field.type.$.computePayloadSize(fieldValue), 8);
}
}
return size;
};
/**
* @param {!mojo.internal.ArraySpec} arraySpec
* @param {number} length
* @return {number}
*/
mojo.internal.computeArraySize = function(arraySpec, length) {
if (arraySpec.elementType === mojo.mojom.Bool) {
return mojo.internal.kArrayHeaderSize + (length + 7) >> 3;
} else {
return mojo.internal.kArrayHeaderSize +
length * arraySpec.elementType.$.arrayElementSize;
}
};
/**
* @param {!DataView} dataView
* @param {number} byteOffset
......@@ -724,10 +758,12 @@ mojo.internal.Message = class {
/**
* @param {number} flags
* @param {number} ordinal
* @param {number=} opt_requestId
* @param {number} requestId
* @param {!mojo.internal.StructSpec} paramStructSpec
* @param {*} value
* @private
*/
constructor(flags, ordinal, opt_requestId) {
constructor(flags, ordinal, requestId, paramStructSpec, value) {
let headerSize, version;
if ((flags &
(mojo.internal.kMessageFlagExpectsResponse |
......@@ -739,282 +775,180 @@ mojo.internal.Message = class {
version = 1;
}
/** @public {!ArrayBuffer} */
this.buffer;
const totalMessageSize = headerSize +
mojo.internal.computeTotalStructSize(paramStructSpec, value);
/** @public {!DataView} */
this.data;
/** @private {number} */
this.bitOffset_;
/** @public {!ArrayBuffer} */
this.buffer = new ArrayBuffer(totalMessageSize);
this.resize_(headerSize);
/* @public {!Array<MojoHandle>} */
this.handles = [];
const header = this.data;
const header = new DataView(this.buffer);
header.setUint32(0, headerSize, mojo.internal.kHostLittleEndian);
header.setUint32(4, version, mojo.internal.kHostLittleEndian);
header.setUint32(8, 0); // Interface ID (only for associated interfaces)
header.setUint32(12, ordinal, mojo.internal.kHostLittleEndian);
header.setUint32(16, flags, mojo.internal.kHostLittleEndian);
header.setUint32(20, 0); // Padding
if (version > 0) {
mojo.internal.setUint64(
this.data, 24,
/** @type {number} */ (opt_requestId));
}
this.handles = [];
if (version > 0)
mojo.internal.setUint64(header, 24, requestId);
/** @private {number} */
this.cursor_ = headerSize;
/** @private {!Array<{pointerOffset: number, execute: Function}>} */
this.deferredEncodings_ = [];
}
this.nextAllocationOffset_ = headerSize;
/**
* @param {number} newByteLength
* @private
*/
resize_(newByteLength) {
if (this.buffer && newByteLength === this.buffer.byteLength)
return;
const newBuffer = new ArrayBuffer(newByteLength);
if (this.buffer)
new Uint8Array(newBuffer).set(new Uint8Array(this.buffer));
this.buffer = newBuffer;
this.data = new DataView(newBuffer);
this.bitOffset_ = 0;
const paramStructData = this.allocate(
mojo.internal.kStructHeaderSize + paramStructSpec.packedSize);
const encoder = new mojo.internal.Encoder(this, paramStructData);
encoder.encodeStructInline(paramStructSpec, value);
}
/**
* @param {number} additionalByteLength
* @private
* @param {number} numBytes
* @return {!DataView} A view into the allocated message bytes.
*/
grow_(additionalByteLength) {
const offset = this.buffer.byteLength;
this.resize_(offset + additionalByteLength);
return offset;
}
/**
* @param {number} alignment
* @private
*/
alignCursor_(alignment) {
if (this.bitOffset_) {
this.cursor_++;
this.bitOffset_ = 0;
}
this.cursor_ = mojo.internal.align(this.cursor_, alignment);
}
/**
* @param {number} alignment
* @private
*/
alignAndGrowToCursor_(alignment) {
this.alignCursor_(alignment);
if (this.cursor_ > this.buffer.byteLength)
this.resize_(this.cursor_);
allocate(numBytes) {
const alignedSize = mojo.internal.align(numBytes, 8);
const view =
new DataView(this.buffer, this.nextAllocationOffset_, alignedSize);
this.nextAllocationOffset_ += alignedSize;
return view;
}
};
/**
* Helps encode outgoing messages. Encoders may be created recursively to encode
* parial message fragments indexed by indirect message offsets, as with encoded
* arrays and nested structs.
*/
mojo.internal.Encoder = class {
/**
* @param {number} amount
* @param {number=} opt_alignment
* @param {!Message} message
* @param {!DataView} data
* @private
*/
advanceCursor_(amount, opt_alignment) {
// As a general rule, a value of N bytes should be aligned to N bytes. This
// is the default behavior.
if (!opt_alignment)
opt_alignment = amount;
this.alignCursor_(opt_alignment);
const offset = this.cursor_;
this.cursor_ += amount;
if (this.cursor_ > this.buffer.byteLength)
this.resize_(this.cursor_);
return offset;
}
/** @private */
executeDeferredEncodings_() {
let encoding;
while (encoding = this.deferredEncodings_.shift()) {
this.alignAndGrowToCursor_(8);
const relativeOffset = this.cursor_ - encoding.pointerOffset;
mojo.internal.setUint64(
this.data, encoding.pointerOffset, relativeOffset);
encoding.execute();
}
}
constructor(message, data) {
/** @private {!Message} */
this.message_ = message;
appendNullOffset() {
// New bytes are already zero-initialized. This is a fast-path since
// encoding null pointers is much more common than encoding actual 64-bit
// integer values.
this.advanceCursor_(8);
/** @private {!DataView} */
this.data_ = data;
}
appendBool(value) {
if (this.cursor_ === this.buffer.byteLength)
this.grow_(1);
const oldValue = this.data.getUint8(this.cursor_);
encodeBool(byteOffset, bitOffset, value) {
const oldValue = this.data_.getUint8(byteOffset);
if (value)
this.data.setUint8(this.cursor_, oldValue | (1 << this.bitOffset_));
this.data_.setUint8(byteOffset, oldValue | (1 << bitOffset));
else
this.data.setUint8(this.cursor_, oldValue & ~(1 << this.bitOffset_));
this.bitOffset_ += 1;
if (this.bitOffset_ == 8) {
this.bitOffset_ = 0;
this.cursor_++;
}
this.data_.setUint8(byteOffset, oldValue & ~(1 << bitOffset));
}
appendInt8(value) {
const offset = this.advanceCursor_(1);
this.data.setInt8(offset, value);
encodeInt8(offset, value) {
this.data_.setInt8(offset, value);
}
appendUint8(value) {
const offset = this.advanceCursor_(1);
this.data.setUint8(offset, value);
encodeUint8(offset, value) {
this.data_.setUint8(offset, value);
}
appendInt16(value) {
const offset = this.advanceCursor_(2);
this.data.setInt16(offset, value, mojo.internal.kHostLittleEndian);
encodeInt16(offset, value) {
this.data._setInt16(offset, value, mojo.internal.kHostLittleEndian);
}
appendUint16(value) {
const offset = this.advanceCursor_(1);
this.data.setUint16(offset, value, mojo.internal.kHostLittleEndian);
encodeUint16(offset, value) {
this.data_.setUint16(offset, value, mojo.internal.kHostLittleEndian);
}
appendInt32(value) {
const offset = this.advanceCursor_(4);
this.data.setInt32(offset, value, mojo.internal.kHostLittleEndian);
encodeInt32(offset, value) {
this.data_.setInt32(offset, value, mojo.internal.kHostLittleEndian);
}
appendUint32(value) {
const offset = this.advanceCursor_(4);
this.data.setUint32(offset, value, mojo.internal.kHostLittleEndian);
encodeUint32(offset, value) {
this.data_.setUint32(offset, value, mojo.internal.kHostLittleEndian);
}
appendInt64(value) {
const offset = this.advanceCursor_(8);
mojo.internal.setInt64(this.data, offset, value);
encodeInt64(offset, value) {
mojo.internal.setInt64(this.data_, offset, value);
}
appendUint64(value) {
const offset = this.advanceCursor_(8);
mojo.internal.setUint64(this.data, offset, value);
encodeUint64(offset, value) {
mojo.internal.setUint64(this.data_, offset, value);
}
appendFloat(value) {
const offset = this.advanceCursor_(4);
this.data.setFloat32(offset, value, mojo.internal.kHostLittleEndian);
encodeFloat(offset, value) {
this.data_.setFloat32(offset, value, mojo.internal.kHostLittleEndian);
}
appendDouble(value) {
const offset = this.advanceCursor_(8);
this.data.setFloat64(offset, value, mojo.internal.kHostLittleEndian);
encodeDouble(offset, value) {
this.data_.setFloat64(offset, value, mojo.internal.kHostLittleEndian);
}
appendHandle(value) {
this.appendUint32(this.handles.length);
this.handles.push(value);
encodeHandle(offset, value) {
this.encodeUint32(offset, this.message_.handles.length);
this.message_.handles.push(value);
}
appendString(value) {
encodeString(offset, value) {
if (!mojo.internal.Message.textEncoder)
mojo.internal.Message.textEncoder = new TextEncoder('utf-8');
this.appendArray(
{elementType: mojo['mojom']['Uint8']},
if (typeof value !== 'string')
throw new Error('Unxpected non-string value for string field.');
this.encodeArray(
{elementType: mojo['mojom']['Uint8']}, offset,
mojo.internal.Message.textEncoder.encode(value));
}
deferEncoding(encoder) {
this.deferredEncodings_.push({
pointerOffset: this.advanceCursor_(8),
execute: encoder,
});
}
/**
* @param {!mojo.internal.ArraySpec} arraySpec
* @param {*} value
*/
appendArray(arraySpec, value) {
this.deferEncoding(this.appendArrayInline.bind(this, arraySpec, value));
encodeOffset(offset, absoluteOffset) {
this.encodeUint64(offset, absoluteOffset - this.data_.byteOffset - offset);
}
/**
* @param {!mojo.internal.ArraySpec} arraySpec
* @param {number} offset
* @param {*} value
*/
appendArrayInline(arraySpec, value) {
let size;
if (arraySpec.elementType === mojo.mojom.Bool) {
size = mojo.internal.kArrayHeaderSize + (value.length + 7) >> 3;
} else {
size = mojo.internal.kArrayHeaderSize +
value.length * arraySpec.elementType.$.arrayElementSize;
}
encodeArray(arraySpec, offset, value) {
const arraySize = mojo.internal.computeArraySize(arraySpec, value.length);
const arrayData = this.message_.allocate(arraySize);
const arrayEncoder = new mojo.internal.Encoder(this.message_, arrayData);
this.encodeOffset(offset, arrayData.byteOffset);
const header = this.advanceCursor_(8);
this.data.setUint32(header, size, mojo.internal.kHostLittleEndian);
this.data.setUint32(
header + 4, value.length, mojo.internal.kHostLittleEndian);
arrayEncoder.encodeUint32(0, arraySize);
arrayEncoder.encodeUint32(4, value.length);
let byteOffset = 8;
if (arraySpec.elementType === mojo.mojom.Bool) {
let bit = 0;
let index = header + 8;
let byte = 0;
let bitOffset = 0;
for (const e of value) {
if (bit == 8) {
this.data.setUint8(index, byte);
bit = 0;
byte = 0;
index++;
arrayEncoder.encodeBool(byteOffset, bitOffset, e);
bitOffset++;
if (bitOffset == 8) {
bitOffset = 0;
byteOffset++;
}
if (e)
byte += (1 << bit);
bit++;
}
this.data.setUint8(index, byte);
this.alignAndGrowToCursor_(8);
return;
}
for (const e of value) {
if (e === null) {
if (!arraySpec.elementNullable) {
throw new Error(
'Trying to send a null element in an array of ' +
'non-nullable elements');
} else {
for (const e of value) {
if (e === null) {
if (!arraySpec.elementNullable) {
throw new Error(
'Trying to send a null element in an array of ' +
'non-nullable elements');
}
arraySpec.elementType.$.encodeNull(arrayEncoder, byteOffset);
}
arraySpec.elementType.$.encodeNull();
arraySpec.elementType.$.encode(e, arrayEncoder, byteOffset, 0);
byteOffset += arraySpec.elementType.$.arrayElementSize;
}
arraySpec.elementType.$.encode(e, this);
}
this.alignAndGrowToCursor_(8);
}
/**
* @param {!mojo.internal.MapSpec} mapSpec
* @param {number} offset
* @param {*} value
*/
appendMap(mapSpec, value) {
this.deferEncoding(this.appendMapInline.bind(this, mapSpec, value));
}
/**
* @param {!mojo.internal.MapSpec} mapSpec
* @param {*} value
*/
appendMapInline(mapSpec, value) {
encodeMap(mapSpec, offset, value) {
let keys, values;
if (value instanceof Map) {
keys = Array.from(value.keys());
......@@ -1024,35 +958,45 @@ mojo.internal.Message = class {
values = keys.map(k => value[k]);
}
const header = this.advanceCursor_(mojo.internal.kStructHeaderSize, 8);
this.data.setUint32(
header, mojo.internal.kMapDataSize, mojo.internal.kHostLittleEndian);
this.data.setUint32(header + 4, 0);
this.appendArray({elementType: mapSpec.keyType}, keys);
this.appendArray(
const mapData = this.message_.allocate(mojo.internal.kMapDataSize);
const mapEncoder = new mojo.internal.Encoder(this.message_, mapData);
this.encodeOffset(offset, mapData.byteOffset);
mapEncoder.encodeUint32(0, mojo.internal.kMapDataSize);
mapEncoder.encodeUint32(4, 0);
mapEncoder.encodeArray({elementType: mapSpec.keyType}, 8, keys);
mapEncoder.encodeArray(
{
elementType: mapSpec.valueType,
elementNullable: mapSpec.valueNullable
},
values);
16, values);
}
/**
* @param {!mojo.internal.StructSpec} structSpec
* @param {number} offset
* @param {!Object} value
* @return {number} The offset into the message where the struct begins.
*/
appendStruct(structSpec, value) {
this.deferEncoding(this.appendStructInline.bind(this, structSpec, value));
encodeStruct(structSpec, offset, value) {
const structData = this.message_.allocate(
mojo.internal.kStructHeaderSize + structSpec.packedSize);
const structEncoder = new mojo.internal.Encoder(this.message_, structData);
this.encodeOffset(offset, structData.byteOffset);
structEncoder.encodeStructInline(structSpec, value);
}
/**
* @param {!mojo.internal.StructSpec} structSpec
* @param {!Object} value
*/
appendStructInline(structSpec, value) {
const header = this.advanceCursor_(8);
encodeStructInline(structSpec, value) {
this.encodeUint32(
0, mojo.internal.kStructHeaderSize + structSpec.packedSize);
this.encodeUint32(4, 0); // TODO: Support versioning.
for (const field of structSpec.fields) {
const byteOffset = mojo.internal.kStructHeaderSize + field.packedOffset;
if (!value || !(value instanceof Object) ||
mojo.internal.isNullOrUndefined(value[field.name])) {
if (!field.nullable) {
......@@ -1060,17 +1004,13 @@ mojo.internal.Message = class {
structSpec.name + ' missing value for non-nullable ' +
'field "' + field.name + '"');
}
field.type.$.encodeNull(this);
field.type.$.encodeNull(this, byteOffset);
continue;
}
field.type.$.encode(value[field.name], this);
field.type.$.encode(
value[field.name], this, byteOffset, field.packedBitOffset);
}
this.alignAndGrowToCursor_(8);
this.data.setUint32(
header, this.cursor_ - header, mojo.internal.kHostLittleEndian);
this.data.setUint32(header + 4, 0); // TODO: Support versioning.
}
};
......@@ -1086,112 +1026,62 @@ mojo.internal.Decoder = class {
/**
* @param {!DataView} data
* @param {!Array<MojoHandle>} handles
* @param {number} cursor
* @private
*/
constructor(data, handles, cursor) {
constructor(data, handles) {
/** @private {!DataView} */
this.data_ = data;
/** @private {!Array<MojoHandle>} */
this.handles_ = handles;
/** @private {number} */
this.cursor_ = cursor;
/** @private {number} */
this.bitOffset_ = 0;
/** @private {number} */
this.lastBoolOffset_ = 0;
}
/**
* @param {number} alignment
* @private
*/
alignCursor_(alignment) {
if (this.bitOffset_ > 0) {
this.cursor_++;
this.bitOffset_ = 0;
}
this.cursor_ = mojo.internal.align(this.cursor_, alignment);
}
/**
* @param {number} amount
* @return {number}
* @private
*/
alignAndAdvanceCursor_(amount) {
this.alignCursor_(amount);
const cursor = this.cursor_;
this.cursor_ += amount;
return cursor;
}
decodeBool() {
if (this.cursor_ != this.lastBoolOffset_)
this.bitOffset_ = 0;
const offset = this.cursor_;
const bit = this.bitOffset_++;
this.lastBoolOffset_ = offset;
if (this.bitOffset_ == 8) {
this.cursor_++;
this.bitOffset_ = 0;
}
return !!(this.data_.getUint8(offset) & (1 << bit));
decodeBool(byteOffset, bitOffset) {
return !!(this.data_.getUint8(byteOffset) & (1 << bitOffset));
}
decodeInt8() {
return this.data_.getInt8(this.cursor_++);
decodeInt8(offset) {
return this.data_.getInt8(offset);
}
decodeUint8() {
return this.data_.getUint8(this.cursor_++);
decodeUint8(offset) {
return this.data_.getUint8(offset);
}
decodeInt16() {
return this.data_.getInt16(
this.alignAndAdvanceCursor_(2), mojo.internal.kHostLittleEndian);
decodeInt16(offset) {
return this.data_.getInt16(offset, mojo.internal.kHostLittleEndian);
}
decodeUint16() {
return this.data_.getUint16(
this.alignAndAdvanceCursor_(2), mojo.internal.kHostLittleEndian);
decodeUint16(offset) {
return this.data_.getUint16(offset, mojo.internal.kHostLittleEndian);
}
decodeInt32() {
return this.data_.getInt32(
this.alignAndAdvanceCursor_(4), mojo.internal.kHostLittleEndian);
decodeInt32(offset) {
return this.data_.getInt32(offset, mojo.internal.kHostLittleEndian);
}
decodeUint32() {
return this.data_.getUint32(
this.alignAndAdvanceCursor_(4), mojo.internal.kHostLittleEndian);
decodeUint32(offset) {
return this.data_.getUint32(offset, mojo.internal.kHostLittleEndian);
}
decodeInt64() {
return mojo.internal.getInt64(this.data_, this.alignAndAdvanceCursor_(8));
decodeInt64(offset) {
return mojo.internal.getInt64(this.data_, offset);
}
decodeUint64() {
return mojo.internal.getUint64(this.data_, this.alignAndAdvanceCursor_(8));
decodeUint64(offset) {
return mojo.internal.getUint64(this.data_, offset);
}
decodeFloat() {
return this.data_.getFloat32(
this.alignAndAdvanceCursor_(4), mojo.internal.kHostLittleEndian);
decodeFloat(offset) {
return this.data_.getFloat32(offset, mojo.internal.kHostLittleEndian);
}
decodeDouble() {
return this.data_.getFloat64(
this.alignAndAdvanceCursor_(4), mojo.internal.kHostLittleEndian);
decodeDouble(offset) {
return this.data_.getFloat64(offset, mojo.internal.kHostLittleEndian);
}
decodeHandle() {
const index = this.data_.getUint32(
this.alignAndAdvanceCursor_(4), mojo.internal.kHostLittleEndian);
decodeHandle(offset) {
const index = this.data_.getUint32(offset, mojo.internal.kHostLittleEndian);
if (index == 0xffffffff)
return null;
if (index >= this.handles_.length)
......@@ -1199,66 +1089,55 @@ mojo.internal.Decoder = class {
return this.handles_[index];
}
decodeString() {
decodeString(offset) {
if (!mojo.internal.Decoder.textDecoder)
mojo.internal.Decoder.textDecoder = new TextDecoder('utf-8');
return mojo.internal.Decoder.textDecoder.decode(
new Uint8Array(this.decodeArray({
elementType: mojo.mojom.Uint8,
})).buffer);
new Uint8Array(this.decodeArray(
{
elementType: mojo.mojom.Uint8,
},
offset))
.buffer);
}
decodeOffset() {
this.alignCursor_(8);
const cursor = this.cursor_;
const offset = this.decodeUint64();
if (offset == 0)
decodeOffset(offset) {
const relativeOffset = this.decodeUint64(offset);
if (relativeOffset == 0)
return 0;
return cursor + offset;
return this.data_.byteOffset + offset + relativeOffset;
}
/**
* @param {!mojo.internal.ArraySpec} arraySpec
* @return {!Array}
*/
decodeArray(arraySpec) {
const arrayOffset = this.decodeOffset();
decodeArray(arraySpec, offset) {
const arrayOffset = this.decodeOffset(offset);
if (!arrayOffset)
return null;
const elementDecoder =
new mojo.internal.Decoder(this.data_, this.handles_, arrayOffset);
return elementDecoder.decodeArrayInline(arraySpec)
}
const arrayDecoder = new mojo.internal.Decoder(
new DataView(this.data_.buffer, arrayOffset), this.handles_);
/**
* @param {!mojo.internal.ArraySpec} arraySpec
* @return {!Array}
*/
decodeArrayInline(arraySpec) {
const size = this.decodeUint32();
const numElements = this.decodeUint32();
const size = arrayDecoder.decodeUint32(0);
const numElements = arrayDecoder.decodeUint32(4);
if (!numElements)
return [];
const result = [];
if (arraySpec.elementType === mojo.mojom.Bool) {
let bit = 8;
let byteValue = this.decodeUint8();
for (let i = 0; i < numElements; ++i) {
if (bit == 8) {
bit = 0;
byteValue = this.decodeUint8();
}
result.push(!!(byteValue & (1 << bit)));
++bit;
}
for (let i = 0; i < numElements; ++i)
result.push(arrayDecoder.decodeBool(8 + (i >> 3), i % 8));
} else {
let byteOffset = 8;
for (let i = 0; i < numElements; ++i) {
const element = arraySpec.elementType.$.decode(this);
const element =
arraySpec.elementType.$.decode(arrayDecoder, byteOffset, 0);
if (element === null && !arraySpec.elementNullable)
throw new Error('Received unexpected array element');
result.push(element);
byteOffset += arraySpec.elementType.$.arrayElementSize;
}
}
return result;
......@@ -1268,29 +1147,26 @@ mojo.internal.Decoder = class {
* @param {!mojo.internal.MapSpec} mapSpec
* @return {!Object|!Map}
*/
decodeMap(mapSpec) {
const mapOffset = this.decodeOffset();
decodeMap(mapSpec, offset) {
const mapOffset = this.decodeOffset(offset);
if (!mapOffset)
return null;
const mapStructSize =
this.data_.getUint32(mapOffset, mojo.internal.kHostLittleEndian);
const mapStructVersion =
this.data_.getUint32(mapOffset + 4, mojo.internal.kHostLittleEndian);
const mapDecoder = new mojo.internal.Decoder(
new DataView(this.data_.buffer, mapOffset), this.handles_);
const mapStructSize = mapDecoder.decodeUint32(0);
const mapStructVersion = mapDecoder.decodeUint32(4);
if (mapStructSize != mojo.internal.kMapDataSize || mapStructVersion != 0)
throw new Error('Received invalid map data');
const keysDecoder = new mojo.internal.Decoder(
this.data_, this.handles_,
mojo.internal.getUint64(this.data_, mapOffset + 8) + mapOffset + 8);
const valuesDecoder = new mojo.internal.Decoder(
this.data_, this.handles_,
mojo.internal.getUint64(this.data_, mapOffset + 16) + mapOffset + 16);
const keys = keysDecoder.decodeArray({elementType: mapSpec.keyType});
const values = valuesDecoder.decodeArray({
elementType: mapSpec.valueType,
elementNullable: mapSpec.valueNullable
});
const keys = mapDecoder.decodeArray({elementType: mapSpec.keyType}, 8);
const values = mapDecoder.decodeArray(
{
elementType: mapSpec.valueType,
elementNullable: mapSpec.valueNullable
},
16);
if (keys.length != values.length)
throw new Error('Received invalid map data');
if (!mapSpec.keyType.$.isValidObjectKeyType) {
......@@ -1310,13 +1186,13 @@ mojo.internal.Decoder = class {
* @param {!mojo.internal.StructSpec} structSpec
* @return {Object}
*/
decodeStruct(structSpec) {
const structOffset = this.decodeOffset();
decodeStruct(structSpec, offset) {
const structOffset = this.decodeOffset(offset);
if (!structOffset)
return null;
const decoder =
new mojo.internal.Decoder(this.data_, this.handles_, structOffset);
const decoder = new mojo.internal.Decoder(
new DataView(this.data_.buffer, structOffset), this.handles_);
return decoder.decodeStructInline(structSpec);
}
......@@ -1325,11 +1201,13 @@ mojo.internal.Decoder = class {
* @return {!Object}
*/
decodeStructInline(structSpec) {
const size = this.decodeUint32();
const version = this.decodeUint32();
const size = this.decodeUint32(0);
const version = this.decodeUint32(4);
const result = {};
for (const field of structSpec.fields) {
const value = field.type.$.decode(this);
const byteOffset = mojo.internal.kStructHeaderSize + field.packedOffset;
const value =
field.type.$.decode(this, byteOffset, field.packedBitOffset);
if (value === null && !field.nullable) {
throw new Error(
'Received ' + structSpec.name + ' with invalid null field ' +
......@@ -1340,16 +1218,16 @@ mojo.internal.Decoder = class {
return result;
}
decodeInterfaceProxy(type) {
const handle = this.decodeHandle();
const version = this.decodeUint32(); // TODO: support versioning
decodeInterfaceProxy(type, offset) {
const handle = this.decodeHandle(offset);
const version = this.decodeUint32(offset + 4); // TODO: support versioning
if (!handle)
return null;
return new type(handle);
}
decodeInterfaceRequest(type) {
const handle = this.decodeHandle();
decodeInterfaceRequest(type, offset) {
const handle = this.decodeHandle(offset);
if (!handle)
return null;
return new type(handle);
......@@ -1369,11 +1247,10 @@ mojo.internal.Decoder.textDecoder = null;
*/
mojo.internal.serializeAndSendMessage = function(
handle, ordinal, requestId, flags, paramStruct, value) {
const message = new mojo.internal.Message(flags, ordinal, requestId);
message.appendStructInline(
const message = new mojo.internal.Message(
flags, ordinal, requestId,
/* @type {!mojo.internal.StructSpec} */ (paramStruct.$.structSpec),
value);
message.executeDeferredEncodings_();
handle.writeMessage(message.buffer, message.handles);
};
......@@ -1412,8 +1289,8 @@ mojo.internal.deserializeMessageHeader = function(data) {
/**
* @typedef {{
* encode: function(*, !mojo.internal.Message),
* decode: function(!mojo.internal.Decoder):*,
* encode: function(*, !mojo.internal.Encoder, number, number),
* decode: function(!mojo.internal.Decoder, number, number):*,
* isValidObjectKeyType: boolean,
* arrayElementSize: (number|undefined),
* arraySpec: (!mojo.internal.ArraySpec|undefined),
......@@ -1450,6 +1327,8 @@ mojo.internal.MapSpec;
/**
* @typedef {{
* name: string,
* packedOffset: number,
* packedBitOffset: number,
* type: !mojo.internal.MojomType,
* defaultValue: *,
* nullable: boolean,
......@@ -1459,7 +1338,8 @@ mojo.internal.StructFieldSpec;
/**
* @typedef {{
* type: !mojo.internal.MojomType,
* name: string,
* packedSize: number,
* fields: !Array<!mojo.internal.StructFieldSpec>,
* }}
*/
......@@ -1481,10 +1361,12 @@ mojo.mojom = {};
*/
mojo.mojom.Bool = {
$: {
encode: function(value, message) {
message.appendBool(value);
encode: function(value, encoder, byteOffset, bitOffset) {
encoder.encodeBool(byteOffset, bitOffset, value);
},
decode: function(decoder, byteOffset, bitOffset) {
return decoder.decodeBool(byteOffset, bitOffset);
},
decode: decoder => decoder.decodeBool(),
isValidObjectKeyType: true,
},
};
......@@ -1495,10 +1377,12 @@ mojo.mojom.Bool = {
*/
mojo.mojom.Int8 = {
$: {
encode: function(value, message) {
message.appendInt8(value);
encode: function(value, encoder, byteOffset, bitOffset) {
encoder.encodeInt8(byteOffset, value);
},
decode: function(decoder, byteOffset, bitOffset) {
return decoder.decodeInt8(byteOffset);
},
decode: decoder => decoder.decodeInt8(),
arrayElementSize: 1,
isValidObjectKeyType: true,
},
......@@ -1510,10 +1394,12 @@ mojo.mojom.Int8 = {
*/
mojo.mojom.Uint8 = {
$: {
encode: function(value, message) {
message.appendUint8(value);
encode: function(value, encoder, byteOffset, bitOffset) {
encoder.encodeUint8(byteOffset, value);
},
decode: function(decoder, byteOffset, bitOffset) {
return decoder.decodeUint8(byteOffset);
},
decode: decoder => decoder.decodeUint8(),
arrayElementSize: 1,
isValidObjectKeyType: true,
},
......@@ -1525,10 +1411,12 @@ mojo.mojom.Uint8 = {
*/
mojo.mojom.Int16 = {
$: {
encode: function(value, message) {
message.appendInt16(value);
encode: function(value, encoder, byteOffset, bitOffset) {
encoder.encodeInt16(byteOffset, value);
},
decode: function(decoder, byteOffset, bitOffset) {
return decoder.decodeInt16(byteOffset);
},
decode: decoder => decoder.decodeInt16(),
arrayElementSize: 2,
isValidObjectKeyType: true,
},
......@@ -1540,10 +1428,12 @@ mojo.mojom.Int16 = {
*/
mojo.mojom.Uint16 = {
$: {
encode: function(value, message) {
message.appendUint16(value);
encode: function(value, encoder, byteOffset, bitOffset) {
encoder.encodeUint16(byteOffset, value);
},
decode: function(decoder, byteOffset, bitOffset) {
return decoder.decodeUint16(byteOffset);
},
decode: decoder => decoder.decodeUint16(),
arrayElementSize: 2,
isValidObjectKeyType: true,
},
......@@ -1555,10 +1445,12 @@ mojo.mojom.Uint16 = {
*/
mojo.mojom.Int32 = {
$: {
encode: function(value, message) {
message.appendInt32(value);
encode: function(value, encoder, byteOffset, bitOffset) {
encoder.encodeInt32(byteOffset, value);
},
decode: function(decoder, byteOffset, bitOffset) {
return decoder.decodeInt32(byteOffset);
},
decode: decoder => decoder.decodeInt32(),
arrayElementSize: 4,
isValidObjectKeyType: true,
},
......@@ -1570,10 +1462,12 @@ mojo.mojom.Int32 = {
*/
mojo.mojom.Uint32 = {
$: {
encode: function(value, message) {
message.appendUint32(value);
encode: function(value, encoder, byteOffset, bitOffset) {
encoder.encodeUint32(byteOffset, value);
},
decode: function(decoder, byteOffset, bitOffset) {
return decoder.decodeUint32(byteOffset);
},
decode: decoder => decoder.decodeUint32(),
arrayElementSize: 4,
isValidObjectKeyType: true,
},
......@@ -1585,10 +1479,12 @@ mojo.mojom.Uint32 = {
*/
mojo.mojom.Int64 = {
$: {
encode: function(value, message) {
message.appendInt64(value);
encode: function(value, encoder, byteOffset, bitOffset) {
encoder.encodeInt64(byteOffset, value);
},
decode: function(decoder, byteOffset, bitOffset) {
return decoder.decodeInt64(byteOffset);
},
decode: decoder => decoder.decodeInt64(),
arrayElementSize: 8,
isValidObjectKeyType: true,
},
......@@ -1600,10 +1496,12 @@ mojo.mojom.Int64 = {
*/
mojo.mojom.Uint64 = {
$: {
encode: function(value, message) {
message.appendUint64(value);
encode: function(value, encoder, byteOffset, bitOffset) {
encoder.encodeUint64(byteOffset, value);
},
decode: function(decoder, byteOffset, bitOffset) {
return decoder.decodeUint64(byteOffset);
},
decode: decoder => decoder.decodeUint64(),
arrayElementSize: 8,
isValidObjectKeyType: true,
},
......@@ -1615,10 +1513,12 @@ mojo.mojom.Uint64 = {
*/
mojo.mojom.Float = {
$: {
encode: function(value, message) {
message.appendFloat(value);
encode: function(value, encoder, byteOffset, bitOffset) {
encoder.encodeFloat(byteOffset, value);
},
decode: function(decoder, byteOffset, bitOffset) {
return decoder.decodeFloat(byteOffset);
},
decode: decoder => decoder.decodeFloat(),
arrayElementSize: 4,
isValidObjectKeyType: true,
},
......@@ -1630,10 +1530,12 @@ mojo.mojom.Float = {
*/
mojo.mojom.Double = {
$: {
encode: function(value, message) {
message.appendDouble(value);
encode: function(value, encoder, byteOffset, bitOffset) {
encoder.encodeDouble(byteOffset, value);
},
decode: function(decoder, byteOffset, bitOffset) {
return decoder.decodeDouble(byteOffset);
},
decode: decoder => decoder.decodeDouble(),
arrayElementSize: 8,
isValidObjectKeyType: true,
},
......@@ -1645,13 +1547,13 @@ mojo.mojom.Double = {
*/
mojo.mojom.Handle = {
$: {
encode: function(value, message) {
message.appendHandle(value);
encode: function(value, encoder, byteOffset, bitOffset) {
encoder.encodeHandle(byteOffset, value);
},
encodeNull: function(message) {
message.appendUint32(0);
encodeNull: function(encoder, byteOffset) {},
decode: function(decoder, byteOffset, bitOffset) {
return decoder.decodeHandle(byteOffset);
},
decode: decoder => decoder.decodeHandle(),
arrayElementSize: 4,
isValidObjectKeyType: false,
},
......@@ -1663,13 +1565,17 @@ mojo.mojom.Handle = {
*/
mojo.mojom.String = {
$: {
encode: function(value, message) {
message.appendString(value);
encode: function(value, encoder, byteOffset, bitOffset) {
encoder.encodeString(byteOffset, value);
},
encodeNull: function(encoder, byteOffset) {},
decode: function(decoder, byteOffset, bitOffset) {
return decoder.decodeString(byteOffset);
},
encodeNull: function(message) {
message.appendNullOffset();
computePayloadSize: function(value) {
return mojo.internal.computeArraySize(
{elementType: mojo.mojom.Uint8}, value.length);
},
decode: decoder => decoder.decodeString(),
arrayElementSize: 8,
isValidObjectKeyType: true,
}
......@@ -1690,13 +1596,16 @@ mojo.mojom.Array = function(elementType, elementNullable) {
return {
$: {
arraySpec: arraySpec,
encode: function(value, message) {
message.appendArray(arraySpec, value);
encode: function(value, encoder, byteOffset, bitOffset) {
encoder.encodeArray(arraySpec, byteOffset, value);
},
encodeNull: function(message) {
message.appendNullOffset();
encodeNull: function(encoder, byteOffset) {},
decode: function(decoder, byteOffset, bitOffset) {
return decoder.decodeArray(arraySpec, byteOffset);
},
computePayloadSize: function(value) {
return mojo.internal.computeArraySize(arraySpec, value.length);
},
decode: decoder => decoder.decodeArray(arraySpec),
arrayElementSize: 8,
isValidObjectKeyType: false,
},
......@@ -1720,13 +1629,25 @@ mojo.mojom.Map = function(keyType, valueType, valueNullable) {
return {
$: {
mapSpec: mapSpec,
encode: function(value, message) {
message.appendMap(mapSpec, value);
encode: function(value, encoder, byteOffset, bitOffset) {
encoder.encodeMap(mapSpec, byteOffset, value);
},
encodeNull: function(encoder, byteOffset) {},
decode: function(decoder, byteOffset, bitOffset) {
return decoder.decodeMap(mapSpec, byteOffset);
},
encodeNull: function(message) {
message.appendNullOffset();
computePayloadSize: function(value) {
const numEntries =
(value instanceof Map) ? value.size : Object.keys(value).length;
return mojo.internal.kMapDataSize +
mojo.internal.computeArraySize({elementType: keyType}, numEntries) +
mojo.internal.computeArraySize(
{
elementType: valueType,
elementNullable: valueNullable,
},
numEntries);
},
decode: decoder => decoder.decodeMap(mapSpec),
arrayElementSize: 8,
isValidObjectKeyType: false,
},
......@@ -1741,12 +1662,12 @@ mojo.mojom.Map = function(keyType, valueType, valueNullable) {
mojo.mojom.Enum = function(properties) {
return {
$: {
encode: function(value, message) {
encode: function(value, encoder, byteOffset, bitOffset) {
// TODO: Do some sender-side error checking on the input value.
message.appendUint32(value);
encoder.encodeUint32(byteOffset, value);
},
decode: decoder => {
const value = decoder.decodeInt32();
decode: function(decoder, byteOffset, bitOffset) {
const value = decoder.decodeInt32(byteOffset);
// TODO: validate
return value;
},
......@@ -1758,15 +1679,20 @@ mojo.mojom.Enum = function(properties) {
/**
* @param {string} name
* @param {number} packedOffset
* @param {number} packedBitOffset
* @param {!mojo.internal.MojomType} type
* @param {*} defaultValue
* @param {boolean} nullable
* @return {!mojo.mojom.StructFieldSpec}
* @export
*/
mojo.mojom.StructField = function(name, type, defaultValue, nullable) {
mojo.mojom.StructField = function(
name, packedOffset, packedBitOffset, type, defaultValue, nullable) {
return {
name: name,
packedOffset: packedOffset,
packedBitOffset: packedBitOffset,
type: type,
defaultValue: defaultValue,
nullable: nullable,
......@@ -1776,24 +1702,29 @@ mojo.mojom.StructField = function(name, type, defaultValue, nullable) {
/**
* @param {!Object} objectToBlessAsType
* @param {string} name
* @param {number} packedSize
* @param {!Array<!mojo.internal.StructFieldSpec>} fields
* @export
*/
mojo.mojom.Struct = function(objectToBlessAsType, name, fields) {
mojo.mojom.Struct = function(objectToBlessAsType, name, packedSize, fields) {
/** @type {!mojo.internal.StructSpec} */
const structSpec = {
name: name,
packedSize: packedSize,
fields: fields,
};
objectToBlessAsType.$ = {
structSpec: structSpec,
encode: function(value, message) {
message.appendStruct(structSpec, value);
encode: function(value, encoder, byteOffset, bitOffset) {
encoder.encodeStruct(structSpec, byteOffset, value);
},
encodeNull: function(message) {
message.appendNullOffset();
encodeNull: function(encoder, byteOffset) {},
decode: function(decoder, byteOffset, bitOffset) {
return decoder.decodeStruct(structSpec, byteOffset);
},
computePayloadSize: function(value) {
return mojo.internal.computeTotalStructSize(structSpec, value);
},
decode: decoder => decoder.decodeStruct(structSpec),
arrayElementSize: 8,
isValidObjectKeyType: false,
};
......@@ -1808,23 +1739,26 @@ mojo.mojom.InterfaceProxy = function(type) {
$: {
/**
* @param {!{proxy: mojo.internal.InterfaceProxyBase}} value
* @param {!mojo.internal.Message} message
* @param {!mojo.internal.Encoder} encoder
* @param {number} byteOffset
* @param {number} bitOffset
*/
encode: function(value, message) {
encode: function(value, encoder, byteOffset, bitOffset) {
if (!(value instanceof type))
throw new Error('Invalid proxy type. Expected ' + type.name);
if (!value.proxy.handle)
throw new Error('Unexpected null ' + type.name);
message.appendHandle(value.proxy.handle);
message.appendUint32(0); // TODO: Support versioning
encoder.encodeHandle(byteOffset, value.proxy.handle);
encoder.encodeUint32(byteOffset + 4, 0); // TODO: Support versioning
value.proxy.unbind();
},
encodeNull: function(message) {
message.appendUint32(0xffffffff);
message.appendUint32(0);
encodeNull: function(encoder, byteOffset) {
encoder.encodeUint32(byteOffset, 0xffffffff);
},
decode: function(decoder, byteOffset, bitOffset) {
return decoder.decodeInterfaceProxy(type, byteOffset);
},
decode: decoder => decoder.decodeInterfaceProxy(type),
arrayElementSize: 8,
isValidObjectKeyType: false,
},
......@@ -1838,18 +1772,19 @@ mojo.mojom.InterfaceProxy = function(type) {
mojo.mojom.InterfaceRequest = function(type) {
return {
$: {
encode: function(value, message) {
encode: function(value, encoder, byteOffset, bitOffset) {
if (!(value instanceof type))
throw new Error('Invalid request type. Expected ' + type.name);
if (!value.handle)
throw new Error('Unexpected null ' + type.name);
message.appendHandle(value.handle);
encoder.encodeHandle(byteOffset, value.handle);
},
encodeNull: function(encoder, byteOffset) {
encoder.encodeUint32(byteOffset, 0xffffffff);
},
encodeNull: function(message) {
message.appendUint32(0xffffffff);
decode: function(decoder, byteOffset, bitOffset) {
return decoder.decodeInterfaceRequest(type, byteOffset);
},
decode: decoder => decoder.decodeInterfaceRequest(type),
arrayElementSize: 4,
isValidObjectKeyType: false,
},
......@@ -1864,10 +1799,10 @@ mojo.mojom.AssociatedInterfaceProxy = function(type) {
return {
$: {
type: type,
encode: function(value, message) {
encode: function(value, encoder, byteOffset, bitOffset) {
throw new Error('Associated interfaces not supported yet.');
},
decode: decoder => {
decode: function(decoder, byteOffset, bitOffset) {
throw new Error('Associated interfaces not supported yet.');
},
},
......@@ -1882,10 +1817,10 @@ mojo.mojom.AssociatedInterfaceRequest = function(type) {
return {
$: {
type: type,
encode: function(value, message) {
encode: function(value, encoder, byteOffset, bitOffset) {
throw new Error('Associated interfaces not supported yet.');
},
decode: decoder => {
decode: function(decoder, byteOffset, bitOffset) {
throw new Error('Associated interfaces not supported yet.');
},
},
......
......@@ -2,10 +2,13 @@
mojo.mojom.Struct(
{{struct.name}},
'{{struct.name}}',
{{struct.packed|payload_size}},
[
{%- for packed_field in struct.packed.packed_fields %}
{%- for packed_field in struct.packed.packed_fields_in_ordinal_order %}
mojo.mojom.StructField(
'{{packed_field.field.name}}',
'{{packed_field.field.name}}', {{packed_field.offset}},
{% if packed_field.field.kind|is_bool_kind %}{{packed_field.bit}}
{%- else %}0{% endif %},
{{packed_field.field.kind|lite_js_type}},
{{packed_field.field|lite_default_value}},
{%- if packed_field.field.kind.is_nullable %}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment