blob: feec23db9ec4b81fcdc888b1a8c21cd4923e52d2 [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 encoder.
* @private
*/
module.exports = Encoder;
var TypeEncoder = require('./type-encoder.js');
var util = require('../vdl/util.js');
var typeUtil = require('../vdl/type-util.js');
var RawVomWriter = require('./raw-vom-writer.js');
var kind = require('../vdl/kind.js');
var canonicalize = require('../vdl/canonicalize.js');
var stringify = require('../vdl/stringify.js');
var guessType = require('../vdl/guess-type.js');
var BigInt = require('../vdl/big-int');
var BootstrapTypes = require('./bootstrap-types');
var unwrap = require('../vdl/type-util').unwrap;
var wiretype = require('../gen-vdl/v.io/v23/vom');
var endByte = unwrap(wiretype.WireCtrlEnd);
var nilByte = unwrap(wiretype.WireCtrlNil);
var versions = require('./versions.js');
require('../vdl/es6-shim');
/**
* Create an encoder that manages the transmission and marshaling of typed
* values to the other side of a connection.
* @param {module:vanadium.vom.ByteMessageWriter} messageWriter The
* message writer to write to.
* @param {module:vanadim.vom.TypeEncoder} typeEncoder If set, the passed
* in type encoder will be used and the type messages will not appear in
* messageWriter's output.
* @param {number} version vom version (e.g. 0x80, 0x81, ...)
* @constructor
* @memberof module:vanadium.vom
*/
function Encoder(messageWriter, typeEncoder, version) {
this._messageWriter = messageWriter;
if (typeEncoder) {
this._typeEncoder = typeEncoder;
} else {
this._typeEncoder = new TypeEncoder(messageWriter);
}
if (!version) {
version = versions.defaultVersion;
}
this._version = version;
}
/**
* Encodes a value.
* @param {*} val The value to encode
* @param {module:vanadium.vdl.Type} type The type of the value.
*/
Encoder.prototype.encode = function(val, type) {
if (type === undefined) {
type = guessType(val);
}
// Canonicalize and validate the value. This prepares the value for encoding.
val = canonicalize.fill(val, type);
var typeId = this._typeEncoder.encodeType(type);
this._typeIds = [];
var writer = new RawVomWriter(this._version);
this._encodeValue(val, type, writer, false);
this._messageWriter.writeValueMessage(typeId,
typeUtil.shouldSendLength(type), typeUtil.hasAnyOrTypeObject(type),
this._typeIds, writer.getBytes());
};
Encoder.prototype._encodeValue = function(v, t, writer, omitEmpty) {
v = typeUtil.unwrap(v);
switch (t.kind) {
case kind.BOOL:
if (!v && omitEmpty) {
return false;
}
writer.writeBool(v);
return true;
case kind.BYTE:
if (!v && omitEmpty) {
return false;
}
writer.writeByte(v);
return true;
case kind.UINT16:
case kind.UINT32:
case kind.UINT64:
if (!v && omitEmpty) {
return false;
}
if ((v instanceof BigInt) && omitEmpty && v._sign === 0) {
return false;
}
writer.writeUint(v);
return true;
case kind.INT8:
if (this._version === versions.version80) {
throw new Error('int8 is not supported in VOM version 0x80');
} // jshint ignore:line
case kind.INT16:
case kind.INT32:
case kind.INT64:
if (!v && omitEmpty) {
return false;
}
if ((v instanceof BigInt) && omitEmpty && v._sign === 0) {
return false;
}
writer.writeInt(v);
return true;
case kind.FLOAT32:
case kind.FLOAT64:
if (!v && omitEmpty) {
return false;
}
writer.writeFloat(v);
return true;
case kind.COMPLEX64:
case kind.COMPLEX128:
if (typeof v === 'object') {
if (v.real === 0 && v.imag === 0 && omitEmpty) {
return false;
}
writer.writeFloat(v.real);
writer.writeFloat(v.imag);
return true;
} else if (typeof v === 'number' && omitEmpty) {
if (v === 0) {
return false;
}
writer.writeFloat(v);
writer.writeFloat(0);
return true;
}
return false;
case kind.STRING:
if (v === '' && omitEmpty) {
return false;
}
writer.writeString(v);
return true;
case kind.ENUM:
return this._encodeEnum(v, t, writer, omitEmpty);
case kind.LIST:
return this._encodeList(v, t, writer, omitEmpty);
case kind.ARRAY:
return this._encodeArray(v, t, writer, omitEmpty);
case kind.SET:
return this._encodeSet(v, t, writer, omitEmpty);
case kind.MAP:
return this._encodeMap(v, t, writer, omitEmpty);
case kind.STRUCT:
return this._encodeStruct(v, t, writer, omitEmpty);
case kind.UNION:
return this._encodeUnion(v, t, writer, omitEmpty);
case kind.ANY:
return this._encodeAny(v, writer, omitEmpty);
case kind.OPTIONAL:
return this._encodeOptional(v, t, writer, omitEmpty);
case kind.TYPEOBJECT:
var typeId = this._typeEncoder.encodeType(v);
if (typeId === BootstrapTypes.definitions.ANY.id && omitEmpty) {
return false;
}
if (this._version === versions.version80) {
writer.writeUint(typeId);
} else {
writer.writeUint(this._addTypeId(typeId));
}
return true;
default:
throw new Error('Unknown kind ' + t.kind);
}
};
Encoder.prototype._encodeEnum = function(v, t, writer, omitEmpty) {
var labelIndex = t.labels.indexOf(v);
if (omitEmpty && labelIndex === 0) {
return false;
}
writer.writeUint(labelIndex);
return true;
};
Encoder.prototype._encodeList = function(v, t, writer, omitEmpty) {
if (v.length === 0 && omitEmpty) {
return false;
}
writer.writeUint(v.length);
this._writeSequence(v, t, writer);
return true;
};
Encoder.prototype._encodeArray = function(v, t, writer) {
writer.writeUint(0);
this._writeSequence(v, t, writer);
return true;
};
Encoder.prototype._encodeSet = function(v, t, writer, omitEmpty) {
if (v.size === 0 && omitEmpty) {
return false;
}
writer.writeUint(v.size);
v.forEach(function(value, key) {
this._encodeValue(key, t.key, writer);
}, this);
return true;
};
Encoder.prototype._encodeMap = function(v, t, writer, omitEmpty) {
if (v.size === 0 && omitEmpty) {
return false;
}
writer.writeUint(v.size);
v.forEach(function(value, key) {
this._encodeValue(key, t.key, writer);
this._encodeValue(value, t.elem, writer);
}, this);
return true;
};
Encoder.prototype._encodeStruct = function(v, t, writer, omitEmpty) {
// Encode the fields.
var hasWrittenFields = false;
t.fields.forEach(function(fieldDesc, fieldIndex) {
var pos = writer.getPos();
writer.writeUint(fieldIndex);
var fieldVal = v[util.uncapitalize(fieldDesc.name)];
var valueWritten = this._encodeValue(fieldVal, fieldDesc.type, writer,
true);
if (!valueWritten) {
writer.seekBack(pos);
} else {
hasWrittenFields = true;
}
}, this);
if (omitEmpty && !hasWrittenFields) {
return false;
}
writer.writeControlByte(endByte);
return true;
};
Encoder.prototype._writeSequence = function(v, t, writer) {
if (t.elem.kind === kind.BYTE) {
// Byte sequences can be copied directly from the input Uint8Array.
writer._writeRawBytes(v);
return;
}
for (var i = 0; i < v.length; i++) {
var elem = v[i];
var elemType = t.elem;
this._encodeValue(elem, elemType, writer);
}
};
Encoder.prototype._encodeOptional = function(v, t, writer, omitEmpty) {
if (v === null || v === undefined) {
if (omitEmpty) {
return false;
}
writer.writeControlByte(nilByte);
return true;
}
this._encodeValue(v, t.elem, writer, false);
return true;
};
Encoder.prototype._encodeAny = function(v, writer, omitEmpty) {
if (v === null || v === undefined) {
if (omitEmpty) {
return false;
}
writer.writeControlByte(nilByte);
return true;
}
var t = guessType(v);
var typeId = this._typeEncoder.encodeType(t);
if (this._version === versions.version80) {
writer.writeUint(typeId);
} else {
writer.writeUint(this._addTypeId(typeId));
}
this._encodeValue(v, t, writer, false);
return true;
};
Encoder.prototype._encodeUnion = function(v, t, writer, omitEmpty) {
for (var i = 0; i < t.fields.length; i++) {
var key = t.fields[i].name;
var lowerKey = util.uncapitalize(key);
if (v.hasOwnProperty(lowerKey) && v[lowerKey] !== undefined) {
var pos = writer.getPos();
writer.writeUint(i);
// We can only omit empty values if it is the first field in the
// union. If it is the second or later field, it always has to
// be emitted.
omitEmpty = omitEmpty && i === 0;
var encoded = this._encodeValue(v[lowerKey], t.fields[i].type, writer,
omitEmpty);
if (!encoded) {
writer.seekBack(pos);
return false;
}
return true;
}
}
throw new Error('Union did not encode properly. Received: ' + stringify(v));
};
Encoder.prototype._addTypeId = function(typeId) {
var index = this._typeIds.indexOf(typeId);
if (index !== -1) {
return index;
}
index = this._typeIds.length;
this._typeIds.push(typeId);
return index;
};