// Copyright 2015-2017 Parity Technologies (UK) Ltd. // This file is part of Parity. // Parity is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Parity is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Parity. If not, see . import utf8 from 'utf8'; import Token from '../token/token'; import BytesTaken from './bytesTaken'; import DecodeResult from './decodeResult'; import ParamType from '../spec/paramType/paramType'; import { sliceData } from '../util/slice'; import { asAddress, asBool, asI32, asU32 } from '../util/sliceAs'; import { isArray, isInstanceOf } from '../util/types'; const NULL = '0000000000000000000000000000000000000000000000000000000000000000'; export default class Decoder { static decode (params, data) { if (!isArray(params)) { throw new Error('Parameters should be array of ParamType'); } const slices = sliceData(data); let offset = 0; return params.map((param) => { const result = Decoder.decodeParam(param, slices, offset); offset = result.newOffset; return result.token; }); } static peek (slices, position) { if (!slices || !slices[position]) { return NULL; } return slices[position]; } static takeBytes (slices, position, length) { const slicesLength = Math.floor((length + 31) / 32); let bytesStr = ''; for (let idx = 0; idx < slicesLength; idx++) { bytesStr = `${bytesStr}${Decoder.peek(slices, position + idx)}`; } const bytes = (bytesStr.substr(0, length * 2).match(/.{1,2}/g) || []).map((code) => parseInt(code, 16)); return new BytesTaken(bytes, position + slicesLength); } static decodeParam (param, slices, offset) { if (!isInstanceOf(param, ParamType)) { throw new Error('param should be instanceof ParamType'); } const tokens = []; let taken; let lengthOffset; let length; let newOffset; switch (param.type) { case 'address': return new DecodeResult(new Token(param.type, asAddress(Decoder.peek(slices, offset))), offset + 1); case 'bool': return new DecodeResult(new Token(param.type, asBool(Decoder.peek(slices, offset))), offset + 1); case 'int': return new DecodeResult(new Token(param.type, asI32(Decoder.peek(slices, offset))), offset + 1); case 'uint': return new DecodeResult(new Token(param.type, asU32(Decoder.peek(slices, offset))), offset + 1); case 'fixedBytes': taken = Decoder.takeBytes(slices, offset, param.length); return new DecodeResult(new Token(param.type, taken.bytes), taken.newOffset); case 'bytes': lengthOffset = asU32(Decoder.peek(slices, offset)).div(32).toNumber(); length = asU32(Decoder.peek(slices, lengthOffset)).toNumber(); taken = Decoder.takeBytes(slices, lengthOffset + 1, length); return new DecodeResult(new Token(param.type, taken.bytes), offset + 1); case 'string': if (param.indexed) { taken = Decoder.takeBytes(slices, offset, 32); return new DecodeResult(new Token('fixedBytes', taken.bytes), offset + 1); } lengthOffset = asU32(Decoder.peek(slices, offset)).div(32).toNumber(); length = asU32(Decoder.peek(slices, lengthOffset)).toNumber(); taken = Decoder.takeBytes(slices, lengthOffset + 1, length); const str = taken.bytes.map((code) => String.fromCharCode(code)).join(''); let decoded; try { decoded = utf8.decode(str); } catch (error) { decoded = str; } return new DecodeResult(new Token(param.type, decoded), offset + 1); case 'array': lengthOffset = asU32(Decoder.peek(slices, offset)).div(32).toNumber(); length = asU32(Decoder.peek(slices, lengthOffset)).toNumber(); newOffset = lengthOffset + 1; for (let idx = 0; idx < length; idx++) { const result = Decoder.decodeParam(param.subtype, slices, newOffset); newOffset = result.newOffset; tokens.push(result.token); } return new DecodeResult(new Token(param.type, tokens), offset + 1); case 'fixedArray': newOffset = offset; for (let idx = 0; idx < param.length; idx++) { const result = Decoder.decodeParam(param.subtype, slices, newOffset); newOffset = result.newOffset; tokens.push(result.token); } return new DecodeResult(new Token(param.type, tokens), newOffset); default: throw new Error(`Invalid param type ${param.type} in decodeParam`); } } }