blob: 28e8561e76c2cbaabde6cc8c618219b5c07f28e7 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/**
* @fileoverview Represents a VOM decoder.
* @private
*/
module.exports = Decoder;
var canonicalize = require('../vdl/canonicalize.js');
var TypeDecoder = require('./type-decoder.js');
var kind = require('../vdl/kind.js');
var Registry = require('../vdl/registry.js');
var types = require('../vdl/types.js');
var util = require('../vdl/util.js');
var unwrap = require('../vdl/type-util').unwrap;
var wiretype = require('../gen-vdl/v.io/v23/vom');
var nativeTypeRegistry = require('../vdl/native-type-registry');
var Deferred = require('../lib/deferred');
var Promise = require('../lib/promise');
var TaskSequence = require('../lib/task-sequence');
var promiseFor = require('../lib/async-helper').promiseFor;
var promiseWhile = require('../lib/async-helper').promiseWhile;
var endByte = unwrap(wiretype.WireCtrlEnd);
var nilByte = unwrap(wiretype.WireCtrlNil);
/**
* Create a decoder to read objects from the provided message reader.
* Decode has the option of returning a deeply-wrapped object, or an object only
* wrapped at the top-level.
* @param {module:vanadium.vom.ByteArrayMessageReader} reader The message
* reader.
* @param {boolean=} deepWrap Whether to deeply wrap. Defaults to false.
* @param {module:vanadium.vom.TypeDecoder} typeDecoder The type decoder to
* use. This can be null.
* @memberof module:vanadium.vom
* @constructor
*/
function Decoder(messageReader, deepWrap, typeDecoder) {
this._messageReader = messageReader;
this._typeDecoder = typeDecoder || new TypeDecoder();
this._deepWrap = false;
this._tasks = new TaskSequence();
}
/*
* TODO(bprosnitz) We will want to be able to decode when we get callbacks.
* Revisit this API.
*/
/**
* Decodes the next object off of the message reader.
* @return {object} The next object or null if no more objects are available.
*/
Decoder.prototype.decode = function(cb) {
var def = new Deferred(cb);
var decoder = this;
this._tasks.addTask(function() {
return decoder._messageReader.nextMessageType(decoder._typeDecoder).
then(function(type) {
if (type === null) {
return null;
}
var reader = decoder._messageReader.rawReader;
return decoder._decodeValue(type, reader, true);
}).then(function(v) {
def.resolve(v);
}, function(err) {
def.reject(err);
});
});
return def.promise;
};
Decoder.prototype._decodeValue = function(t, reader, shouldWrap) {
return this._decodeUnwrappedValue(t, reader).then(function(value) {
// Special: JSValue should be reduced and returned as a native value.
if (types.JSVALUE.equals(t)) {
return canonicalize.reduce(value, types.JSVALUE);
}
if (nativeTypeRegistry.hasNativeType(t)) {
return canonicalize.reduce(value, t);
}
// If this value should be wrapped, apply the constructor.
if (t.kind !== kind.TYPEOBJECT && shouldWrap) {
var Ctor = Registry.lookupOrCreateConstructor(t);
if (Ctor.prototype._wrappedType) {
return new Ctor(value);
}
if (value !== null && value !== undefined) {
Object.defineProperty(value, 'constructor', {
value: Ctor,
});
}
}
return value;
});
};
Decoder.prototype._decodeUnwrappedValue = function(t, reader) {
switch (t.kind) {
case kind.BOOL:
return reader.readBool();
case kind.BYTE:
return reader.readByte();
case kind.UINT16:
case kind.UINT32:
return reader.readUint();
case kind.UINT64:
return reader.readBigUint();
case kind.INT16:
case kind.INT32:
return reader.readInt();
case kind.INT64:
return reader.readBigInt();
case kind.FLOAT32:
case kind.FLOAT64:
return reader.readFloat();
case kind.COMPLEX64:
case kind.COMPLEX128:
return reader.readFloat().then(function(real) {
return reader.readFloat().then(function(imag) {
return {
real: real,
imag: imag
};
});
});
case kind.STRING:
return reader.readString();
case kind.ENUM:
return this._decodeEnum(t, reader);
case kind.LIST:
return this._decodeList(t, reader);
case kind.ARRAY:
return this._decodeArray(t, reader);
case kind.SET:
return this._decodeSet(t, reader);
case kind.MAP:
return this._decodeMap(t, reader);
case kind.STRUCT:
return this._decodeStruct(t, reader);
case kind.UNION:
return this._decodeUnion(t, reader);
case kind.ANY:
return this._decodeAny(reader);
case kind.OPTIONAL:
return this._decodeOptional(t, reader);
case kind.TYPEOBJECT:
var decoder = this;
var typeId;
return reader.readUint().then(function(tId) {
typeId = tId;
return decoder._typeDecoder.lookupType(typeId);
}).then(function(type) {
if (type === undefined) {
throw new Error('Undefined type for TYPEOBJECT id ' + typeId);
}
return type;
});
default:
return Promise.reject(new Error('Support for decoding kind ' + t.kind +
' not yet implemented'));
}
};
Decoder.prototype._decodeEnum = function(t, reader) {
return reader.readUint().then(function(index) {
if (t.labels.length <= index) {
throw new Error('Invalid enum index ' + index);
}
return t.labels[index];
});
};
Decoder.prototype._decodeList = function(t, reader) {
var decoder = this;
return reader.readUint().then(function(len) {
return decoder._readSequence(t, len, reader);
});
};
Decoder.prototype._decodeArray = function(t, reader) {
var decoder = this;
// Consume the zero byte at the beginning of the array.
return reader.readByte().then(function(b) {
if (b !== 0) {
throw new Error('Unexpected length ' + b);
}
return decoder._readSequence(t, t.len, reader);
});
};
Decoder.prototype._readSequence = function(t, len, reader) {
if (t.elem.kind === kind.BYTE) {
// Read byte sequences directly into Uint8Arrays.
// The Uint8Array is created by calling subarray. In node, this means that
// its buffer points to the whole binary_reader buffer. To fix this, we
// recreate the Uint8Array here to avoid exposing it.
return reader._readRawBytes(len).then(function(b) {
return new Uint8Array(b);
});
}
var arr = new Array(len);
var i = 0;
var decoder = this;
return promiseFor(len, function() {
return decoder._decodeValue(t.elem, reader, false).then(function(val) {
arr[i] = val;
i++;
});
}).then(function() {
return arr;
});
};
Decoder.prototype._decodeSet = function(t, reader) {
var decoder = this;
var s = new Set();
return reader.readUint().then(function(len) {
return promiseFor(len, function() {
return decoder._decodeValue(t.key, reader, false).then(function(key) {
s.add(key);
});
});
}).then(function() {
return s;
});
};
Decoder.prototype._decodeMap = function(t, reader) {
var decoder = this;
return reader.readUint().then(function(len) {
var m = new Map();
var i = 0;
if (len > 0) {
return decoder._decodeValue(t.key, reader, false).then(handleKey);
}
return m;
function handleKey(key) {
return decoder._decodeValue(t.elem, reader, false).then(function(value) {
m.set(key, value);
i++;
if (i < len) {
return decoder._decodeValue(t.key, reader, false).then(handleKey);
}
return m;
});
}
});
};
Decoder.prototype._decodeStruct = function(t, reader) {
var decoder = this;
var Ctor = Registry.lookupOrCreateConstructor(t);
var obj = Object.create(Ctor.prototype);
return promiseWhile(notEndByte, readField).then(function() {
return obj;
}).then(function(obj) {
t.fields.forEach(function(field) {
var name = util.uncapitalize(field.name);
if (!obj.hasOwnProperty(name)) {
obj[name] = unwrap(canonicalize.zero(field.type));
}
});
return obj;
});
function notEndByte() {
return reader.tryReadControlByte().then(function(ctrl) {
if (ctrl === endByte) {
return false;
}
if (ctrl) {
throw new Error('Unexpected control byte ' + ctrl);
}
return true;
});
}
function readField() {
var name = '';
return reader.readUint().then(function(nextIndex) {
if (t.fields.length <= nextIndex) {
throw new Error('Struct index ' + nextIndex + ' out of bounds');
}
var field = t.fields[nextIndex];
name = util.uncapitalize(field.name);
return decoder._decodeValue(field.type, reader, false);
}).then(function(val) {
obj[name] = val;
});
}
};
Decoder.prototype._decodeOptional = function(t, reader) {
var decoder = this;
return reader.peekByte().then(function(isNil) {
if (isNil === nilByte) {
// We don't have to wait for the read to finish.
reader.readByte();
return null;
}
return decoder._decodeValue(t.elem, reader, false);
});
};
Decoder.prototype._decodeAny = function(reader) {
var decoder = this;
return reader.tryReadControlByte().then(function(ctrl) {
if (ctrl === nilByte) {
return null;
}
if (ctrl) {
throw new Error('Unexpected control byte ' + ctrl);
}
var typeId;
return reader.readUint().then(function(tId) {
typeId = tId;
return decoder._typeDecoder.lookupType(typeId);
}).then(function(type) {
if (type === undefined) {
throw new Error('Undefined typeid ' + typeId);
}
return decoder._decodeValue(type, reader, true);
});
});
};
Decoder.prototype._decodeUnion = function(t, reader) {
var decoder = this;
var field;
// Find the Union field that was set and decode its value.
return reader.readUint().then(function(fieldIndex) {
if (t.fields.length <= fieldIndex) {
throw new Error('Union index ' + fieldIndex + ' out of bounds');
}
field = t.fields[fieldIndex];
return decoder._decodeValue(field.type, reader, false);
}).then(function(val) {
// Return the Union with a single field set to its decoded value.
var Ctor = Registry.lookupOrCreateConstructor(t);
var obj = Object.create(Ctor.prototype);
obj[util.uncapitalize(field.name)] = val;
return obj;
});
};