Use DataView as applicable (#1186)
* Use DataView as applicable * .skip * Fix hexToU8a length calc * hello world
This commit is contained in:
+2
-2
@@ -8,8 +8,8 @@ Changes:
|
||||
- Add strongly typed `object{Entries, Keys, Values}` utilities
|
||||
- Expand tests for uncovered functions
|
||||
- Sync with upstream Substrate ss58 registry
|
||||
- Optimizations for hex <-> u8a functions
|
||||
- Allow `u8aEq` to use `Uint{8, 16, 32}Array` with correct alignment
|
||||
- Optimize for `hexToU8a` and `u8aToHex` functions
|
||||
- Optimize `u8aEq` to use `DataView` for compares
|
||||
|
||||
|
||||
## 7.8.2 Nov 7, 2021
|
||||
|
||||
@@ -17,7 +17,7 @@ for (let n = 0; n < 256; n++) {
|
||||
for (let i = 0; i < 256; i++) {
|
||||
for (let j = 0; j < 256; j++) {
|
||||
const hex = U8_TO_HEX[i] + U8_TO_HEX[j];
|
||||
const n = (j * 256) + i;
|
||||
const n = (i * 256) + j;
|
||||
|
||||
U16_TO_HEX[n] = hex;
|
||||
HEX_TO_U16[hex] = n;
|
||||
|
||||
@@ -35,6 +35,12 @@ describe('hexToU8a', (): void => {
|
||||
);
|
||||
});
|
||||
|
||||
it('converts known bytes to their correct values', (): void => {
|
||||
expect(
|
||||
hexToU8a('0x68656c6c6f20776f726c64') // hello world (11 bytes, non-aligned)
|
||||
).toEqual(new Uint8Array([0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64]));
|
||||
});
|
||||
|
||||
it('fails when non-hex value provided', (): void => {
|
||||
expect(
|
||||
() => hexToU8a('notahex')
|
||||
|
||||
@@ -28,37 +28,25 @@ export function hexToU8a (_value?: HexString | string | null, bitLength = -1): U
|
||||
|
||||
const value = hexStripPrefix(_value).toLowerCase();
|
||||
const valLength = value.length / 2;
|
||||
const resultLength = Math.ceil(
|
||||
const endLength = Math.ceil(
|
||||
bitLength === -1
|
||||
? valLength
|
||||
: bitLength / 8
|
||||
);
|
||||
const result = new Uint8Array(resultLength);
|
||||
const offset = resultLength > valLength
|
||||
? resultLength - valLength
|
||||
const result = new Uint8Array(endLength);
|
||||
const offset = endLength > valLength
|
||||
? endLength - valLength
|
||||
: 0;
|
||||
const dv = new DataView(result.buffer, offset);
|
||||
const mod = (endLength - offset) % 2;
|
||||
const length = endLength - offset - mod;
|
||||
|
||||
// With offsets, it becomes slightly trickier since we need to ensure the U16
|
||||
// is at the correct alignment as well. Due to this, we just focus on the simple
|
||||
// case where we do no bitlength conversions (which is the most-taken path)
|
||||
if (offset) {
|
||||
for (let i = 0; i < resultLength; i++) {
|
||||
result[i + offset] = HEX_TO_U8[value.substr(i * 2, 2)];
|
||||
}
|
||||
} else {
|
||||
// NOTE: It is only _slightly_ more optimal to use HEX_TO_U16 here as
|
||||
// well. The overhead does not seem to be in the lookup map iteration,
|
||||
// even with 640k inputs
|
||||
const mod = resultLength % 2;
|
||||
const u16 = new Uint16Array(result.buffer, 0, (resultLength - mod) / 2);
|
||||
for (let i = 0; i < length; i += 2) {
|
||||
dv.setUint16(i, HEX_TO_U16[value.substr(i * 2, 4)]);
|
||||
}
|
||||
|
||||
for (let i = 0; i < u16.length; i++) {
|
||||
u16[i] = HEX_TO_U16[value.substr(i * 4, 4)];
|
||||
}
|
||||
|
||||
if (mod) {
|
||||
result[resultLength - 1] = HEX_TO_U8[value.substr(value.length - 2, 2)];
|
||||
}
|
||||
if (mod) {
|
||||
dv.setUint8(length, HEX_TO_U8[value.substr(value.length - 2, 2)]);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -31,6 +31,42 @@ describe('u8aEq', (): void => {
|
||||
).toEqual(true);
|
||||
});
|
||||
|
||||
it('returns false when the contents do not match (with u32 start)', (): void => {
|
||||
expect(
|
||||
u8aEq(
|
||||
new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3]),
|
||||
new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 4])
|
||||
)
|
||||
).toEqual(false);
|
||||
});
|
||||
|
||||
it('returns true when the contents do match (with u32 start)', (): void => {
|
||||
expect(
|
||||
u8aEq(
|
||||
new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3]),
|
||||
new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3])
|
||||
)
|
||||
).toEqual(true);
|
||||
});
|
||||
|
||||
it('returns false when the contents do not match (u32 only)', (): void => {
|
||||
expect(
|
||||
u8aEq(
|
||||
new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]),
|
||||
new Uint8Array([1, 2, 3, 4, 5, 6, 7, 9])
|
||||
)
|
||||
).toEqual(false);
|
||||
});
|
||||
|
||||
it('returns true when the contents do match (u32 only)', (): void => {
|
||||
expect(
|
||||
u8aEq(
|
||||
new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]),
|
||||
new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8])
|
||||
)
|
||||
).toEqual(true);
|
||||
});
|
||||
|
||||
it('returns true with equalvalent hex & Uint8array', (): void => {
|
||||
expect(
|
||||
u8aEq(
|
||||
|
||||
+18
-33
@@ -5,41 +5,26 @@ import type { HexString } from '../types';
|
||||
|
||||
import { u8aToU8a } from './toU8a';
|
||||
|
||||
interface Constructor<T extends Uint32Array | Uint16Array > {
|
||||
BYTES_PER_ELEMENT: number;
|
||||
|
||||
new(...args: unknown[]): T;
|
||||
}
|
||||
|
||||
// Creates a Uint{16, 32}Array, ensuring that the alignment is correct
|
||||
function createArray <T extends Uint32Array | Uint16Array> (Clazz: Constructor<T>, value: Uint8Array): T {
|
||||
// The byteOffset needs to match the size of the data, i.e. for Uint32 it needs to be 4
|
||||
// NOTE: DataView doesn't have this limitation, but getters are slower
|
||||
const align = Clazz.BYTES_PER_ELEMENT;
|
||||
|
||||
return value.byteOffset % align
|
||||
? new Clazz(value.buffer.slice(value.byteOffset), 0, value.length / align)
|
||||
: new Clazz(value.buffer, value.byteOffset, value.length / align);
|
||||
}
|
||||
|
||||
function equalsArray <T extends Uint32Array | Uint16Array | Uint8Array> (a: T, b: T): boolean {
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
if (a[i] !== b[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function equals (a: Uint8Array, b: Uint8Array): boolean {
|
||||
if (a.length === b.length) {
|
||||
// NOTE: We don't do BigUint64Array, at this point it is significantly slower
|
||||
return a.length % Uint32Array.BYTES_PER_ELEMENT
|
||||
? a.length % Uint16Array.BYTES_PER_ELEMENT
|
||||
? equalsArray(a, b)
|
||||
: equalsArray(createArray(Uint16Array, a), createArray(Uint16Array, b))
|
||||
: equalsArray(createArray(Uint32Array, a), createArray(Uint32Array, b));
|
||||
const dvA = new DataView(a.buffer, a.byteOffset);
|
||||
const dvB = new DataView(b.buffer, b.byteOffset);
|
||||
const mod = a.length % 4;
|
||||
const length = a.length - mod;
|
||||
|
||||
for (let i = 0; i < length; i += 4) {
|
||||
if (dvA.getUint32(i) !== dvB.getUint32(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = length; i < a.length; i++) {
|
||||
if (a[i] !== b[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -5,24 +5,19 @@ import type { HexString } from '../types';
|
||||
|
||||
import { U8_TO_HEX, U16_TO_HEX } from '../hex/alphabet';
|
||||
|
||||
const U16_SIZE = Uint16Array.BYTES_PER_ELEMENT;
|
||||
|
||||
/** @internal */
|
||||
function hex (value: Uint8Array): string {
|
||||
const mod = value.length % U16_SIZE;
|
||||
|
||||
// We need alignment of the Uint16Array, see u8aEq for further details
|
||||
const u16 = value.byteOffset % U16_SIZE
|
||||
? new Uint16Array(value.buffer.slice(value.byteOffset), 0, (value.length - mod) / U16_SIZE)
|
||||
: new Uint16Array(value.buffer, value.byteOffset, (value.length - mod) / U16_SIZE);
|
||||
const mod = value.length % 2;
|
||||
const length = value.length - mod;
|
||||
const dv = new DataView(value.buffer, value.byteOffset);
|
||||
let result = '';
|
||||
|
||||
for (let i = 0; i < u16.length; i++) {
|
||||
result += U16_TO_HEX[u16[i]];
|
||||
for (let i = 0; i < length; i += 2) {
|
||||
result += U16_TO_HEX[dv.getUint16(i)];
|
||||
}
|
||||
|
||||
if (mod) {
|
||||
result += U8_TO_HEX[value[value.length - 1]];
|
||||
result += U8_TO_HEX[dv.getUint8(length)];
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
Reference in New Issue
Block a user