From 8a6090842e047627a75798c027824bb22c3a3e7e Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Wed, 21 Mar 2018 17:16:55 +0100 Subject: [PATCH] RLP u8a conversion complexity (#112) * Address u8a conversion complexity * Split RLP decoder steps * Cleanup decode value checks * Encoder lookup table (as per decoder) * Typings for encoders/decoders values --- package.json | 2 +- packages/util-rlp/src/decoder/decode.js | 44 +++++++--------- packages/util-rlp/src/encoder/toU8a.js | 68 +++++++++++++++---------- yarn.lock | 6 +-- 4 files changed, 62 insertions(+), 58 deletions(-) diff --git a/package.json b/package.json index 040b6441e..2339baba1 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "test": "jest --coverage" }, "devDependencies": { - "@polkadot/dev": "^0.17.1", + "@polkadot/dev": "^0.17.2", "lerna": "^2.5.1" } } diff --git a/packages/util-rlp/src/decoder/decode.js b/packages/util-rlp/src/decoder/decode.js index 09a333e8b..b8a316a34 100644 --- a/packages/util-rlp/src/decoder/decode.js +++ b/packages/util-rlp/src/decoder/decode.js @@ -3,7 +3,12 @@ // of the ISC license. See the LICENSE file for details. // @flow -import type { DecodeOutput } from './types'; +import type { DecodeFunc, DecodeOutput } from './types'; + +type Decoder = { + max: number, + fn: (decode: DecodeFunc, input: Uint8Array) => DecodeOutput +}; const decodeListLong = require('./listLong'); const decodeListShort = require('./listShort'); @@ -11,32 +16,19 @@ const decodeNumber = require('./number'); const decodeSingle = require('./single'); const decodeString = require('./string'); +const decoders: Array = [ + { max: 0x7f, fn: decodeSingle }, + { max: 0xb7, fn: decodeString }, + { max: 0xbf, fn: decodeNumber }, + { max: 0xf7, fn: decodeListShort }, + { max: 0xff, fn: decodeListLong } +]; + function decode (input: Uint8Array): DecodeOutput { - const firstByte = input[0]; - - // a single byte whose value is in the [0x00, 0x7f] range, that byte is its own RLP encoding - if (firstByte <= 0x7f) { - return decodeSingle(decode, input); - } - - // string is 0-55 bytes long. A single byte with value 0x80 plus the length of the string followed by the string - // The range of the first byte is [0x80, 0xb7] - if (firstByte <= 0xb7) { - return decodeString(decode, input); - } - - // a number - if (firstByte <= 0xbf) { - return decodeNumber(decode, input); - } - - // a list between 0-55 bytes long - if (firstByte <= 0xf7) { - return decodeListShort(decode, input); - } - - // a list over 55 bytes long - return decodeListLong(decode, input); + return decoders + .find(({ max }) => input[0] <= max) + // $FlowFixMe last entry (listLong) is max uint8 value + .fn(decode, input); } module.exports = decode; diff --git a/packages/util-rlp/src/encoder/toU8a.js b/packages/util-rlp/src/encoder/toU8a.js index b514870d2..ba6bbb132 100644 --- a/packages/util-rlp/src/encoder/toU8a.js +++ b/packages/util-rlp/src/encoder/toU8a.js @@ -3,49 +3,61 @@ // of the ISC license. See the LICENSE file for details. // @flow +type Encoder = { + // flowlint-next-line unclear-type:off + check: (value: any) => boolean, + // flowlint-next-line unclear-type:off + fn: (value: any) => Uint8Array +}; + +const assert = require('@polkadot/util/assert'); const bnToU8a = require('@polkadot/util/bn/toU8a'); const bufferToU8a = require('@polkadot/util/buffer/toU8a'); const hexHasPrefix = require('@polkadot/util/hex/hasPrefix'); const hexToU8a = require('@polkadot/util/hex/toU8a'); -const isInstanceOf = require('@polkadot/util/is/instanceOf'); const isBn = require('@polkadot/util/is/bn'); const isBuffer = require('@polkadot/util/is/buffer'); const isNull = require('@polkadot/util/is/null'); const isNumber = require('@polkadot/util/is/number'); const isString = require('@polkadot/util/is/string'); +const isU8a = require('@polkadot/util/is/u8a'); const isUndefined = require('@polkadot/util/is/undefined'); const numberToU8a = require('@polkadot/util/number/toU8a'); const u8aFromUtf8 = require('@polkadot/util/u8a/fromUtf8'); +function newEmpty (): Uint8Array { + return new Uint8Array([]); +} + +function convertString (value: string): Uint8Array { + if (hexHasPrefix(value)) { + return hexToU8a(value); + } + + return u8aFromUtf8(value); +} + +function convertU8a (value: Uint8Array): Uint8Array { + return value; +} + +const encoders: Array = [ + { check: isNull, fn: newEmpty }, + { check: isUndefined, fn: newEmpty }, + // NOTE: Buffer before U8a + { check: isBuffer, fn: bufferToU8a }, + { check: isU8a, fn: convertU8a }, + { check: isBn, fn: bnToU8a }, + { check: isNumber, fn: numberToU8a }, + { check: isString, fn: convertString } +]; + // flowlint-next-line unclear-type:off module.exports = function toU8a (value: any): Uint8Array { - if (isNull(value) || isUndefined(value)) { - return new Uint8Array([]); - } + const encoder = encoders.find(({ check }) => check(value)); - if (isBuffer(value)) { - return bufferToU8a(value); - } + assert(encoder, 'invalid type'); - if (isInstanceOf(value, Uint8Array)) { - return value; - } - - if (isNumber(value)) { - return numberToU8a(value); - } - - if (isString(value)) { - if (hexHasPrefix(value)) { - return hexToU8a(value); - } - - return u8aFromUtf8(value); - } - - if (isBn(value)) { - return bnToU8a(value); - } - - throw new Error('invalid type'); + // $FlowFixMe value checked above + return encoder.fn(value); }; diff --git a/yarn.lock b/yarn.lock index 1beba0c76..2b61d7f5b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -85,9 +85,9 @@ dependencies: babel-runtime "^6.26.0" -"@polkadot/dev@^0.17.1": - version "0.17.1" - resolved "https://registry.yarnpkg.com/@polkadot/dev/-/dev-0.17.1.tgz#f3b2273b61e37bc0a5b01b3999f2c5ba8f63fdc5" +"@polkadot/dev@^0.17.2": + version "0.17.2" + resolved "https://registry.yarnpkg.com/@polkadot/dev/-/dev-0.17.2.tgz#1009c81f9db1548774361aa23631b4bdf8b7ede9" dependencies: babel-cli "^6.26.0" babel-core "^6.26.0"