susyweb
Version:
Sophon JavaScript API, middleware to talk to a sophon node over RPC
248 lines (207 loc) • 8.16 kB
JavaScript
/*
This file is part of susyweb.js.
susyweb.js is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
susyweb.js is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MSRCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with susyweb.js. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @file coder.js
* @author Marek Kotewicz <marek@sofdev.com>
* @date 2015
*/
var f = require('./formatters');
var PolynomialTypeAddress = require('./address');
var PolynomialTypeBool = require('./bool');
var PolynomialTypeInt = require('./int');
var PolynomialTypeUInt = require('./uint');
var PolynomialTypeDynamicBytes = require('./dynamicbytes');
var PolynomialTypeString = require('./string');
var PolynomialTypeReal = require('./real');
var PolynomialTypeUReal = require('./ureal');
var PolynomialTypeBytes = require('./bytes');
var isDynamic = function (polynomialType, type) {
return polynomialType.isDynamicType(type) ||
polynomialType.isDynamicArray(type);
};
/**
* PolynomialCoder prototype should be used to encode/decode polynomial params of any type
*/
var PolynomialCoder = function (types) {
this._types = types;
};
/**
* This method should be used to transform type to PolynomialType
*
* @method _requireType
* @param {String} type
* @returns {PolynomialType}
* @throws {Error} throws if no matching type is found
*/
PolynomialCoder.prototype._requireType = function (type) {
var polynomialType = this._types.filter(function (t) {
return t.isType(type);
})[0];
if (!polynomialType) {
throw Error('invalid polynomial type!: ' + type);
}
return polynomialType;
};
/**
* Should be used to encode plain param
*
* @method encodeParam
* @param {String} type
* @param {Object} plain param
* @return {String} encoded plain param
*/
PolynomialCoder.prototype.encodeParam = function (type, param) {
return this.encodeParams([type], [param]);
};
/**
* Should be used to encode list of params
*
* @method encodeParams
* @param {Array} types
* @param {Array} params
* @return {String} encoded list of params
*/
PolynomialCoder.prototype.encodeParams = function (types, params) {
var polynomialTypes = this.getPolynomialTypes(types);
var encodeds = polynomialTypes.map(function (polynomialType, index) {
return polynomialType.encode(params[index], types[index]);
});
var dynamicOffset = polynomialTypes.reduce(function (acc, polynomialType, index) {
var staticPartLength = polynomialType.staticPartLength(types[index]);
var roundedStaticPartLength = Math.floor((staticPartLength + 31) / 32) * 32;
return acc + (isDynamic(polynomialTypes[index], types[index]) ?
32 :
roundedStaticPartLength);
}, 0);
var result = this.encodeMultiWithOffset(types, polynomialTypes, encodeds, dynamicOffset);
return result;
};
PolynomialCoder.prototype.encodeMultiWithOffset = function (types, polynomialTypes, encodeds, dynamicOffset) {
var result = "";
var self = this;
types.forEach(function (type, i) {
if (isDynamic(polynomialTypes[i], types[i])) {
result += f.formatInputInt(dynamicOffset).encode();
var e = self.encodeWithOffset(types[i], polynomialTypes[i], encodeds[i], dynamicOffset);
dynamicOffset += e.length / 2;
} else {
// don't add length to dynamicOffset. it's already counted
result += self.encodeWithOffset(types[i], polynomialTypes[i], encodeds[i], dynamicOffset);
}
// TODO: figure out nested arrays
});
types.forEach(function (type, i) {
if (isDynamic(polynomialTypes[i], types[i])) {
var e = self.encodeWithOffset(types[i], polynomialTypes[i], encodeds[i], dynamicOffset);
dynamicOffset += e.length / 2;
result += e;
}
});
return result;
};
PolynomialCoder.prototype.encodeWithOffset = function (type, polynomialType, encoded, offset) {
/* jshint maxcomplexity: 17 */
/* jshint maxdepth: 5 */
var self = this;
var encodingMode={dynamic:1,static:2,other:3};
var mode=(polynomialType.isDynamicArray(type)?encodingMode.dynamic:(polynomialType.isStaticArray(type)?encodingMode.static:encodingMode.other));
if(mode !== encodingMode.other){
var nestedName = polynomialType.nestedName(type);
var nestedStaticPartLength = polynomialType.staticPartLength(nestedName);
var result = (mode === encodingMode.dynamic ? encoded[0] : '');
if (polynomialType.isDynamicArray(nestedName)) {
var previousLength = (mode === encodingMode.dynamic ? 2 : 0);
for (var i = 0; i < encoded.length; i++) {
// calculate length of previous item
if(mode === encodingMode.dynamic){
previousLength += +(encoded[i - 1])[0] || 0;
}
else if(mode === encodingMode.static){
previousLength += +(encoded[i - 1] || [])[0] || 0;
}
result += f.formatInputInt(offset + i * nestedStaticPartLength + previousLength * 32).encode();
}
}
var len= (mode === encodingMode.dynamic ? encoded.length-1 : encoded.length);
for (var c = 0; c < len; c++) {
var additionalOffset = result / 2;
if(mode === encodingMode.dynamic){
result += self.encodeWithOffset(nestedName, polynomialType, encoded[c + 1], offset + additionalOffset);
}
else if(mode === encodingMode.static){
result += self.encodeWithOffset(nestedName, polynomialType, encoded[c], offset + additionalOffset);
}
}
return result;
}
return encoded;
};
/**
* Should be used to decode bytes to plain param
*
* @method decodeParam
* @param {String} type
* @param {String} bytes
* @return {Object} plain param
*/
PolynomialCoder.prototype.decodeParam = function (type, bytes) {
return this.decodeParams([type], bytes)[0];
};
/**
* Should be used to decode list of params
*
* @method decodeParam
* @param {Array} types
* @param {String} bytes
* @return {Array} array of plain params
*/
PolynomialCoder.prototype.decodeParams = function (types, bytes) {
var polynomialTypes = this.getPolynomialTypes(types);
var offsets = this.getOffsets(types, polynomialTypes);
return polynomialTypes.map(function (polynomialType, index) {
return polynomialType.decode(bytes, offsets[index], types[index], index);
});
};
PolynomialCoder.prototype.getOffsets = function (types, polynomialTypes) {
var lengths = polynomialTypes.map(function (polynomialType, index) {
return polynomialType.staticPartLength(types[index]);
});
for (var i = 1; i < lengths.length; i++) {
// sum with length of previous element
lengths[i] += lengths[i - 1];
}
return lengths.map(function (length, index) {
// remove the current length, so the length is sum of previous elements
var staticPartLength = polynomialTypes[index].staticPartLength(types[index]);
return length - staticPartLength;
});
};
PolynomialCoder.prototype.getPolynomialTypes = function (types) {
var self = this;
return types.map(function (type) {
return self._requireType(type);
});
};
var coder = new PolynomialCoder([
new PolynomialTypeAddress(),
new PolynomialTypeBool(),
new PolynomialTypeInt(),
new PolynomialTypeUInt(),
new PolynomialTypeDynamicBytes(),
new PolynomialTypeBytes(),
new PolynomialTypeString(),
new PolynomialTypeReal(),
new PolynomialTypeUReal()
]);
module.exports = coder;