diff --git a/packages/candid/src/idl.test.ts b/packages/candid/src/idl.test.ts index b851c7ad6..c25a55354 100644 --- a/packages/candid/src/idl.test.ts +++ b/packages/candid/src/idl.test.ts @@ -152,6 +152,37 @@ test('IDL encoding (tuple)', () => { ); }); +test('IDL encoding (arraybuffer)', () => { + test_( + IDL.Vec(IDL.Nat8), + new Uint8Array([0, 1, 2, 3]), + '4449444c016d7b01000400010203', + 'Array of Nat8s', + ); + test_( + IDL.Vec(IDL.Int8), + new Int8Array([0, 1, 2, 3]), + '4449444c016d7701000400010203', + 'Array of Int8s', + ); + test_( + IDL.Vec(IDL.Int16), + new Int16Array([0, 1, 2, 3, 32767, -1]), + '4449444c016d760100060000010002000300ff7fffff', + 'Array of Int16s', + ); + test_( + IDL.Vec(IDL.Nat64), + new BigUint64Array([BigInt(0), BigInt(1), BigInt(1) << BigInt(60), BigInt(13)]), + '4449444c016d780100040000000000000000010000000000000000000000000000100d00000000000000', + 'Array of Nat64s', + ); + IDL.encode([IDL.Vec(IDL.Nat8)], [new Uint8Array()]); + IDL.encode([IDL.Vec(IDL.Nat8)], [new Uint8Array(100).fill(42)]); + IDL.encode([IDL.Vec(IDL.Nat16)], [new Uint16Array(200).fill(42)]); + expect(() => IDL.encode([IDL.Vec(IDL.Int8)], [new Uint16Array(10).fill(420)])).toThrow(/Invalid vec int8 argument/); +}); + test('IDL encoding (array)', () => { // Array test_( diff --git a/packages/candid/src/idl.ts b/packages/candid/src/idl.ts index 508128f28..9b62d8388 100644 --- a/packages/candid/src/idl.ts +++ b/packages/candid/src/idl.ts @@ -621,7 +621,7 @@ export class FloatClass extends PrimitiveType { * Represents an IDL fixed-width Int(n) */ export class FixedIntClass extends PrimitiveType { - constructor(private _bits: number) { + constructor(public _bits: number) { super(); } @@ -724,10 +724,19 @@ export class FixedNatClass extends PrimitiveType { /** * Represents an IDL Array + * + * Arrays of fixed-sized nat/int type (e.g. nat8), are encoded from and decoded to TypedArrays (e.g. Uint8Array). + * Arrays of float or other non-primitive types are encoded/decoded as untyped array in Javascript. + * * @param {Type} t */ export class VecClass extends ConstructType { // If true, this vector is really a blob and we can just use memcpy. + // + // NOTE: + // With support of encoding/dencoding of TypedArrays, this optimization is + // only used when plain array of bytes are passed as encoding input in order + // to be backward compatible. private _blobOptimization = false; constructor(protected _type: Type) { @@ -742,7 +751,10 @@ export class VecClass extends ConstructType { } public covariant(x: any): x is T[] { - return Array.isArray(x) && x.every(v => this._type.covariant(v)); + // Special case for ArrayBuffer + const bits = this._type instanceof FixedNatClass ? this._type.bits : (this._type instanceof FixedIntClass ? this._type._bits : 0); + return (ArrayBuffer.isView(x) && bits == (x as any).BYTES_PER_ELEMENT * 8) + || (Array.isArray(x) && x.every(v => this._type.covariant(v))); } public encodeValue(x: T[]) { @@ -750,6 +762,9 @@ export class VecClass extends ConstructType { if (this._blobOptimization) { return concat(len, new Uint8Array(x as unknown as number[])); } + if (ArrayBuffer.isView(x)) { + return concat(len, new Uint8Array(x.buffer)); + } const buf = new Pipe(new ArrayBuffer(len.byteLength + x.length), 0); buf.write(len); for (const d of x) { @@ -773,8 +788,35 @@ export class VecClass extends ConstructType { throw new Error('Not a vector type'); } const len = Number(lebDecode(b)); - if (this._blobOptimization) { - return [...new Uint8Array(b.read(len))] as unknown as T[]; + + if (this._type instanceof FixedNatClass) { + if (this._type.bits == 8) { + return new Uint8Array(b.read(len)) as unknown as T[]; + } + if (this._type.bits == 16) { + return new Uint16Array(b.read(len * 2)) as unknown as T[]; + } + if (this._type.bits == 32) { + return new Uint32Array(b.read(len * 4)) as unknown as T[]; + } + if (this._type.bits == 64) { + return new BigUint64Array(b.read(len * 8)) as unknown as T[]; + } + } + + if (this._type instanceof FixedIntClass) { + if (this._type._bits == 8) { + return new Int8Array(b.read(len)) as unknown as T[]; + } + if (this._type._bits == 16) { + return new Int16Array(b.read(len * 2)) as unknown as T[]; + } + if (this._type._bits == 32) { + return new Int32Array(b.read(len * 4)) as unknown as T[]; + } + if (this._type._bits == 64) { + return new BigInt64Array(b.read(len * 8)) as unknown as T[]; + } } const rets: T[] = [];