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:
Jaco Greeff
2018-10-03 10:52:16 +02:00
committed by GitHub
parent 0719e02e82
commit a38d1faea7
10 changed files with 158 additions and 222 deletions
+12 -31
View File
@@ -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
View File
@@ -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
);
}
}
+2 -2
View File
@@ -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
});
});
+10
View File
@@ -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);
+86
View File
@@ -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)]);
});
});
});
+19 -8
View File
@@ -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)) {
-78
View File
@@ -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);
});
});
-63
View File
@@ -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);
}
}
+2 -2
View File
@@ -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 {
+13 -13
View File
@@ -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 {