Remove Length, variable-prefix calls Compact directly (#190)
* Remove Length, variable-prefix calls Compact directly * Only store originalLength * s/Length/Compact/ is test description
This commit is contained in:
+12
-31
@@ -2,56 +2,37 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { AnyU8a } from './types';
|
||||
|
||||
import u8aConcat from '@polkadot/util/u8a/concat';
|
||||
|
||||
import Length from './codec/Length';
|
||||
import Compact from './codec/Compact';
|
||||
import U8a from './codec/U8a';
|
||||
|
||||
// A Bytes. The significant difference between this and a normal Uint8Array is that
|
||||
// this version allows for length-encoding. (i.e. it is a variable-item codec, the same
|
||||
// as what is found in String and Array)
|
||||
// as what is found in Text and Vector)
|
||||
export default class Bytes extends U8a {
|
||||
protected _length: Length;
|
||||
|
||||
constructor (value?: AnyU8a) {
|
||||
super(value);
|
||||
|
||||
this._length = new Length(this.raw.length);
|
||||
}
|
||||
|
||||
get length (): number {
|
||||
return this._length.toNumber();
|
||||
return this.raw.length;
|
||||
}
|
||||
|
||||
byteLength (): number {
|
||||
return this._length.byteLength() + this.length;
|
||||
}
|
||||
|
||||
fromJSON (input: any): Bytes {
|
||||
super.fromJSON(input);
|
||||
|
||||
this._length.setValue(this.raw.length);
|
||||
|
||||
return this;
|
||||
return this.length + Compact.encode(this.length).length;
|
||||
}
|
||||
|
||||
fromU8a (input: Uint8Array): Bytes {
|
||||
this._length.fromU8a(input);
|
||||
const [offset, length] = Compact.decode(input);
|
||||
|
||||
const length = this._length.toNumber();
|
||||
const offset = this._length.byteLength();
|
||||
|
||||
super.fromU8a(input.subarray(offset, offset + length));
|
||||
super.fromU8a(input.subarray(offset, offset + length.toNumber()));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
toU8a (isBare?: boolean): Uint8Array {
|
||||
return u8aConcat(
|
||||
this._length.toU8a(isBare),
|
||||
this.raw
|
||||
);
|
||||
return isBare
|
||||
? this.raw
|
||||
: u8aConcat(
|
||||
Compact.encode(this.length),
|
||||
this.raw
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+14
-25
@@ -7,7 +7,7 @@ import u8aToUtf8 from '@polkadot/util/u8a/toUtf8';
|
||||
import u8aConcat from '@polkadot/util/u8a/concat';
|
||||
|
||||
import Base from './codec/Base';
|
||||
import Length from './codec/Length';
|
||||
import Compact from './codec/Compact';
|
||||
|
||||
// This is a string wrapper, along with the length. It is used both for strings as well
|
||||
// as stuff like documentation.
|
||||
@@ -18,16 +18,12 @@ import Length from './codec/Length';
|
||||
// wraps the `Balance`, `T::AccountId`, etc. The reasoning - with a "TypeString"
|
||||
// we can nicely strip types down like "T::AcountId" -> "AccountId"
|
||||
export default class Text extends Base<string> {
|
||||
protected _length: Length;
|
||||
|
||||
constructor (value: Text | string = '') {
|
||||
super(
|
||||
value instanceof Text
|
||||
? value.raw
|
||||
: value.trim()
|
||||
);
|
||||
|
||||
this._length = new Length(value.length);
|
||||
}
|
||||
|
||||
get length (): number {
|
||||
@@ -35,25 +31,19 @@ export default class Text extends Base<string> {
|
||||
}
|
||||
|
||||
byteLength (): number {
|
||||
// NOTE String gets trimmed in the fromU8a, so get the original length from the length
|
||||
// placeholder and the associtated byteLength from that encoding
|
||||
return this._length.toNumber() +
|
||||
this._length.byteLength();
|
||||
return this.length + Compact.encode(this.length).length;
|
||||
}
|
||||
|
||||
fromJSON (input: any): Text {
|
||||
this.raw = `${input || ''}`;
|
||||
fromU8a (input: Uint8Array): Text {
|
||||
const [offset, length] = Compact.decode(input);
|
||||
|
||||
this.raw = u8aToUtf8(input.subarray(offset, offset + length.toNumber()));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
fromU8a (input: Uint8Array): Text {
|
||||
this._length.fromU8a(input);
|
||||
|
||||
const length = this._length.toNumber();
|
||||
const offset = this._length.byteLength();
|
||||
|
||||
this.raw = u8aToUtf8(input.subarray(offset, offset + length)).trim();
|
||||
fromJSON (input: any): Text {
|
||||
this.raw = `${input || ''}`;
|
||||
|
||||
return this;
|
||||
}
|
||||
@@ -67,14 +57,13 @@ export default class Text extends Base<string> {
|
||||
}
|
||||
|
||||
toU8a (isBare?: boolean): Uint8Array {
|
||||
// NOTE Since we trim in the fromU8a, here we re-calculate the actual length
|
||||
const encoded = u8aFromUtf8(this.raw);
|
||||
|
||||
this._length.setValue(encoded.length);
|
||||
|
||||
return u8aConcat(
|
||||
this._length.toU8a(isBare),
|
||||
encoded
|
||||
);
|
||||
return isBare
|
||||
? encoded
|
||||
: u8aConcat(
|
||||
Compact.encode(this.length),
|
||||
encoded
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ describe('Type', () => {
|
||||
expect(
|
||||
new Type(
|
||||
new Text(' Box<Proposal> ')
|
||||
).length
|
||||
).toEqual('Proposal'.length); // eslint-disable-line
|
||||
).toString()
|
||||
).toEqual('Proposal'); // eslint-disable-line
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,12 +12,21 @@ const ALLOWED_BOXES = ['Vec', 'Option'];
|
||||
// what string provides us, however we also "tweak" the types received from the runtime, i.e.
|
||||
// we remove the `T::` prefixes found in some types for consistency accross implementation.
|
||||
export default class Type extends Text {
|
||||
private _originalLength: number = 0;
|
||||
|
||||
constructor (value?: Text | string) {
|
||||
super(value);
|
||||
|
||||
this._cleanupTypes();
|
||||
}
|
||||
|
||||
// NOTE Length is used in the decoding calculations, so return the original (pre-cleanup)
|
||||
// length of the data. Since toU8a is disabled, this does not affect encoding, but rather
|
||||
// only the decoding leg, allowing the decoders to work with original pointers
|
||||
get length (): number {
|
||||
return this._originalLength;
|
||||
}
|
||||
|
||||
fromJSON (input: any): Type {
|
||||
super.fromJSON(input);
|
||||
|
||||
@@ -55,6 +64,7 @@ export default class Type extends Text {
|
||||
// `PropIndex` -> `ProposalIndex` (implementation looks the same, however meant as diff)
|
||||
];
|
||||
|
||||
this._originalLength = this.raw.length;
|
||||
this.raw = mappings.reduce((result, fn) => {
|
||||
return fn(result);
|
||||
}, this.raw);
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright 2017-2018 @polkadot/types authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import BN from 'bn.js';
|
||||
|
||||
import Compact from './Compact';
|
||||
import UInt from './UInt';
|
||||
|
||||
describe('Compact', () => {
|
||||
describe('encode', () => {
|
||||
it('encodes short u8', () => {
|
||||
expect(
|
||||
Compact.encode(18)
|
||||
).toEqual(
|
||||
new Uint8Array([18 << 2])
|
||||
);
|
||||
});
|
||||
|
||||
it('encodes max u8 values', () => {
|
||||
expect(
|
||||
Compact.encode(new UInt(63))
|
||||
).toEqual(
|
||||
new Uint8Array([0b11111100])
|
||||
);
|
||||
});
|
||||
|
||||
it('encodes basic u16 value', () => {
|
||||
expect(
|
||||
Compact.encode(511)
|
||||
).toEqual(
|
||||
new Uint8Array([0b11111101, 0b00000111])
|
||||
);
|
||||
});
|
||||
|
||||
it('encodes basic ua6 (not at edge)', () => {
|
||||
expect(
|
||||
Compact.encode(111)
|
||||
).toEqual(
|
||||
new Uint8Array([0xbd, 0x01])
|
||||
);
|
||||
});
|
||||
|
||||
it('encodes basic u32 values (short)', () => {
|
||||
expect(
|
||||
Compact.encode(0xffff)
|
||||
).toEqual(
|
||||
new Uint8Array([254, 255, 3, 0])
|
||||
);
|
||||
});
|
||||
|
||||
it('encodes basic u32 values (full)', () => {
|
||||
expect(
|
||||
Compact.encode(0xfffffff9)
|
||||
).toEqual(
|
||||
new Uint8Array([3, 249, 255, 255, 255])
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('decode', () => {
|
||||
it('decoded u8 value', () => {
|
||||
expect(
|
||||
Compact.decode(new Uint8Array([0b11111100]))
|
||||
).toEqual([1, new BN(63)]);
|
||||
});
|
||||
|
||||
it('decodes from same u16 encoded value', () => {
|
||||
expect(
|
||||
Compact.decode(new Uint8Array([0b11111101, 0b00000111]))
|
||||
).toEqual([2, new BN(511)]);
|
||||
});
|
||||
|
||||
it('decodes from same u32 encoded value (short)', () => {
|
||||
expect(
|
||||
Compact.decode(new Uint8Array([254, 255, 3, 0]))
|
||||
).toEqual([4, new BN(0xffff)]);
|
||||
});
|
||||
|
||||
it('decodes from same u32 encoded value (full)', () => {
|
||||
expect(
|
||||
Compact.decode(new Uint8Array([3, 249, 255, 255, 255]))
|
||||
).toEqual([5, new BN(0xfffffff9)]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -3,15 +3,21 @@
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import BN from 'bn.js';
|
||||
import bnToBn from '@polkadot/util/bn/toBn';
|
||||
import bnToU8a from '@polkadot/util/bn/toU8a';
|
||||
import u8aConcat from '@polkadot/util/u8a/concat';
|
||||
import u8aToBn from '@polkadot/util/u8a/toBn';
|
||||
import toU8a from '@polkadot/util/u8a/toU8a';
|
||||
|
||||
import UInt from './UInt';
|
||||
|
||||
// TODO Could allow for 64 & 128 https://github.com/paritytech/parity-codec/pull/6
|
||||
type BitLength = 32;
|
||||
|
||||
const MAX_U8 = new BN(2).pow(new BN(8 - 2)).subn(1);
|
||||
const MAX_U16 = new BN(2).pow(new BN(16 - 2)).subn(1);
|
||||
const MAX_U32 = new BN(2).pow(new BN(32 - 2)).subn(1);
|
||||
|
||||
type BitLength = 32;
|
||||
const DEFAULT_BITLENGTH = 32;
|
||||
|
||||
// A new compact length-encoding algorithm. It performs the same function as Length, however
|
||||
// differs in that it uses a variable number of bytes to do the actual encoding. From the Rust
|
||||
@@ -29,23 +35,28 @@ type BitLength = 32;
|
||||
//
|
||||
// Note: we use *LOW BITS* of the LSB in LE encoding to encode the 2 bit key.
|
||||
export default class Compact {
|
||||
static decode (input: Uint8Array, bitLength: BitLength): BN {
|
||||
static decode (_input: Uint8Array | string, bitLength: BitLength = DEFAULT_BITLENGTH): [number, BN] {
|
||||
const input = toU8a(_input);
|
||||
const flag = input[0] & 0b11;
|
||||
|
||||
if (flag === 0b00) {
|
||||
return new BN(input[0]).shrn(2);
|
||||
return [1, new BN(input[0]).shrn(2)];
|
||||
} else if (flag === 0b01) {
|
||||
return u8aToBn(input.slice(0, 2), true).shrn(2);
|
||||
return [2, u8aToBn(input.slice(0, 2), true).shrn(2)];
|
||||
} else if (flag === 0b10) {
|
||||
return u8aToBn(input.slice(0, 4), true).shrn(2);
|
||||
return [4, u8aToBn(input.slice(0, 4), true).shrn(2)];
|
||||
}
|
||||
|
||||
const byteLength = bitLength / 8;
|
||||
|
||||
return u8aToBn(input.subarray(1, 1 + byteLength), true);
|
||||
return [byteLength + 1, u8aToBn(input.subarray(1, 1 + byteLength), true)];
|
||||
}
|
||||
|
||||
static encode (length: BN, bitLength: BitLength): Uint8Array {
|
||||
static encode (_length: UInt | BN | number, bitLength: BitLength = DEFAULT_BITLENGTH): Uint8Array {
|
||||
const length = _length instanceof UInt
|
||||
? _length.toBn()
|
||||
: bnToBn(_length);
|
||||
|
||||
if (length.lte(MAX_U8)) {
|
||||
return new Uint8Array([length.toNumber() << 2]);
|
||||
} else if (length.lte(MAX_U16)) {
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
// Copyright 2017-2018 @polkadot/types authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import Length from './Length';
|
||||
import UInt from './UInt';
|
||||
|
||||
describe('Length', () => {
|
||||
it('encodes short u8', () => {
|
||||
expect(
|
||||
new Length(18).toU8a()
|
||||
).toEqual(
|
||||
new Uint8Array([18 << 2])
|
||||
);
|
||||
});
|
||||
|
||||
it('encodes max u8 values', () => {
|
||||
expect(
|
||||
new Length(new UInt(63)).toU8a()
|
||||
).toEqual(
|
||||
new Uint8Array([0b11111100])
|
||||
);
|
||||
});
|
||||
|
||||
it('encodes basic u16 value', () => {
|
||||
expect(
|
||||
new Length(511).toU8a()
|
||||
).toEqual(
|
||||
new Uint8Array([0b11111101, 0b00000111])
|
||||
);
|
||||
});
|
||||
|
||||
it('encodes basic ua6 (not at edge)', () => {
|
||||
expect(
|
||||
new Length(111).toHex()
|
||||
).toEqual('0xbd01');
|
||||
});
|
||||
|
||||
it('decodes from same u16 encoded value', () => {
|
||||
expect(
|
||||
new Length()
|
||||
.fromU8a(new Uint8Array([0b11111101, 0b00000111]))
|
||||
.toNumber()
|
||||
).toEqual(511);
|
||||
});
|
||||
|
||||
it('encodes basic u32 values (short)', () => {
|
||||
expect(
|
||||
new Length(0xffff).toU8a()
|
||||
).toEqual(
|
||||
new Uint8Array([254, 255, 3, 0])
|
||||
);
|
||||
});
|
||||
|
||||
it('decodes from same u32 encoded value (short)', () => {
|
||||
expect(
|
||||
new Length()
|
||||
.fromU8a(new Uint8Array([254, 255, 3, 0]))
|
||||
.toNumber()
|
||||
).toEqual(0xffff);
|
||||
});
|
||||
|
||||
it('encodes basic u32 values (full)', () => {
|
||||
expect(
|
||||
new Length(0xfffffff9).toU8a()
|
||||
).toEqual(
|
||||
new Uint8Array([3, 249, 255, 255, 255])
|
||||
);
|
||||
});
|
||||
|
||||
it('decodes from same u32 encoded value (full)', () => {
|
||||
expect(
|
||||
new Length()
|
||||
.fromU8a(new Uint8Array([3, 249, 255, 255, 255]))
|
||||
.toNumber()
|
||||
).toEqual(0xfffffff9);
|
||||
});
|
||||
});
|
||||
@@ -1,63 +0,0 @@
|
||||
// Copyright 2017-2018 @polkadot/types authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { AnyNumber } from '../types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import bnToBn from '@polkadot/util/bn/toBn';
|
||||
import u8aToHex from '@polkadot/util/u8a/toHex';
|
||||
|
||||
import Base from './Base';
|
||||
import Compact from './Compact';
|
||||
import UInt from './UInt';
|
||||
|
||||
type BitLength = 32;
|
||||
|
||||
// A Length-prefix that can be added to any variable length stream of bytes, e.g. String,
|
||||
// Vectors and Bytes
|
||||
//
|
||||
// FIXME Not crazy about the use of Length. If we look at Vector, Bytes & String,
|
||||
// the implementations are basically the same. Vector is slightly different since
|
||||
// it iterates over the length. We need a cleaner way where we have less copied
|
||||
// code and a more generic implementation around the use of Length. Looking at
|
||||
// Array or Struct, the same type of wrapper would be useful here.
|
||||
export default class Length extends Base<BN> {
|
||||
private _bitLength: BitLength;
|
||||
|
||||
constructor (value: AnyNumber = new BN(0), bitLength: BitLength = 32) {
|
||||
super(
|
||||
UInt.decode(value)
|
||||
);
|
||||
|
||||
this._bitLength = bitLength;
|
||||
}
|
||||
|
||||
byteLength (): number {
|
||||
return this.toU8a().length;
|
||||
}
|
||||
|
||||
fromU8a (input: Uint8Array): Length {
|
||||
this.raw = Compact.decode(input, this._bitLength);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
toHex (): string {
|
||||
return u8aToHex(this.toU8a());
|
||||
}
|
||||
|
||||
toNumber (): number {
|
||||
return this.raw.toNumber();
|
||||
}
|
||||
|
||||
toU8a (isBare?: boolean): Uint8Array {
|
||||
return isBare
|
||||
? new Uint8Array()
|
||||
: Compact.encode(this.raw, this._bitLength);
|
||||
}
|
||||
|
||||
setValue (value: BN | number): void {
|
||||
this.raw = bnToBn(value);
|
||||
}
|
||||
}
|
||||
@@ -23,11 +23,11 @@ export default class U8a extends Base<Uint8Array> {
|
||||
}
|
||||
|
||||
get length (): number {
|
||||
return this.byteLength();
|
||||
return this.raw.length;
|
||||
}
|
||||
|
||||
byteLength (): number {
|
||||
return this.raw.length;
|
||||
return this.length;
|
||||
}
|
||||
|
||||
fromJSON (input: any): U8a {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import u8aConcat from '@polkadot/util/u8a/concat';
|
||||
|
||||
import Base from './Base';
|
||||
import Length from './Length';
|
||||
import Compact from './Compact';
|
||||
|
||||
// This manages codec arrays. Intrernally it keeps track of the length (as decoded) and allows
|
||||
// construction with the passed `Type` in the constructor. It aims to be an array-like structure,
|
||||
@@ -15,7 +15,6 @@ import Length from './Length';
|
||||
export default class Vector <
|
||||
T extends Base
|
||||
> extends Base<Array<T>> {
|
||||
private _length: Length;
|
||||
private _Type: { new(value?: any): T };
|
||||
|
||||
constructor (Type: { new(value?: any): T }, value: Array<any> = [] as Array<any>) {
|
||||
@@ -27,7 +26,6 @@ export default class Vector <
|
||||
)
|
||||
);
|
||||
|
||||
this._length = new Length(value.length);
|
||||
this._Type = Type;
|
||||
}
|
||||
|
||||
@@ -50,7 +48,7 @@ export default class Vector <
|
||||
byteLength (): number {
|
||||
return this.raw.reduce((total, raw) => {
|
||||
return total + raw.byteLength();
|
||||
}, this._length.byteLength());
|
||||
}, Compact.encode(this.length).length);
|
||||
}
|
||||
|
||||
filter (fn: (item: T, index?: number) => any): Array<T> {
|
||||
@@ -74,10 +72,9 @@ export default class Vector <
|
||||
}
|
||||
|
||||
fromU8a (input: Uint8Array): Vector<T> {
|
||||
this._length.fromU8a(input);
|
||||
let [offset, _length] = Compact.decode(input);
|
||||
const length = _length.toNumber();
|
||||
|
||||
const length = this._length.toNumber();
|
||||
let offset = this._length.byteLength();
|
||||
this.raw = [];
|
||||
|
||||
for (let index = 0; index < length; index++) {
|
||||
@@ -100,7 +97,6 @@ export default class Vector <
|
||||
|
||||
push (item: T): void {
|
||||
this.raw.push(item);
|
||||
this._length.setValue(this.raw.length);
|
||||
}
|
||||
|
||||
reduce <O> (fn: (result: O, item: T, index?: number) => O, initial: O): O {
|
||||
@@ -114,12 +110,16 @@ export default class Vector <
|
||||
}
|
||||
|
||||
toU8a (isBare?: boolean): Uint8Array {
|
||||
return u8aConcat(
|
||||
this._length.toU8a(isBare),
|
||||
...this.raw.map((entry) =>
|
||||
entry.toU8a(isBare)
|
||||
)
|
||||
const encoded = this.raw.map((entry) =>
|
||||
entry.toU8a(isBare)
|
||||
);
|
||||
|
||||
return isBare
|
||||
? u8aConcat(...encoded)
|
||||
: u8aConcat(
|
||||
Compact.encode(this.length),
|
||||
...encoded
|
||||
);
|
||||
}
|
||||
|
||||
toString (): string {
|
||||
|
||||
Reference in New Issue
Block a user