idlDecode function
Decode a binary value @param retTypes - Types expected in the buffer. @param bytes - hex-encoded string, or buffer. @returns Value deserialised to JS type
Implementation
List idlDecode(List<CType> retTypes, Uint8List bytes) {
final b = Pipe(bytes);
if (bytes.lengthInBytes < magicNumber.length) {
throw 'Message length smaller than magic number';
}
final magic =
Uint8List.fromList(safeRead(b, magicNumber.length)).u8aToString();
if (magic != magicNumber) {
throw 'Wrong magic number: $magic';
}
// : [Array<[IDLTypeIds, any]>, number[]]
List<dynamic> readTypeTable(Pipe pipe) {
// Array<[IDLTypeIds, any]>;
var typeTable = [];
var len = lebDecode(pipe).toInt();
for (var i = 0; i < len; i++) {
var ty = slebDecode(pipe).toInt();
switch (ty) {
case IDLTypeIds.Opt:
case IDLTypeIds.Vector:
{
var t = slebDecode(pipe).toInt();
typeTable.add([ty, t]);
break;
}
case IDLTypeIds.Record:
case IDLTypeIds.Variant:
{
var fields = List.from([]);
var objectLength = lebDecode(pipe).toInt();
// ignore: prefer_typing_uninitialized_variables
var prevHash;
while (objectLength-- > 0) {
var hash = lebDecode(pipe).toInt();
if (hash >= pow(2, 32)) {
throw 'field id out of 32-bit range';
}
if (prevHash is num && prevHash >= hash) {
throw 'field id collision or not sorted';
}
prevHash = hash;
var t = slebDecode(pipe).toInt();
fields.add([hash, t]);
}
typeTable.add([ty, fields]);
break;
}
case IDLTypeIds.Func:
{
for (var k = 0; k < 2; k++) {
var funcLength = lebDecode(pipe).toInt();
while (funcLength-- > 0) {
slebDecode(pipe);
}
}
var annLen = lebDecode(pipe).toInt();
safeRead(pipe, annLen);
typeTable.add([ty, null]);
break;
}
case IDLTypeIds.Service:
{
var servLength = lebDecode(pipe).toInt();
while (servLength-- > 0) {
var l = lebDecode(pipe).toInt();
safeRead(pipe, l);
slebDecode(pipe);
}
typeTable.add([ty, null]);
break;
}
default:
throw 'Illegal op_code: $ty';
}
}
var rawList = <int>[];
var length = lebDecode(pipe).toInt();
for (var i = 0; i < length; i++) {
rawList.add(slebDecode(pipe).toInt());
}
return [typeTable, rawList];
}
var typeTableRead = readTypeTable(b);
var rawTable = typeTableRead[0] as List<dynamic>; //Array<[IDLTypeIds, any]>
var rawTypes = typeTableRead[1] as List<int>;
if (rawTypes.length < retTypes.length) {
throw 'Wrong number of return values';
}
var table = rawTable.map((_) => RecClass()).toList();
CType getType(int t) {
if (t < -24) {
throw 'future value not supported';
}
if (t < 0) {
switch (t) {
case -1:
return IDL.Null;
case -2:
return IDL.Bool;
case -3:
return IDL.Nat;
case -4:
return IDL.Int;
case -5:
return IDL.Nat8;
case -6:
return IDL.Nat16;
case -7:
return IDL.Nat32;
case -8:
return IDL.Nat64;
case -9:
return IDL.Int8;
case -10:
return IDL.Int16;
case -11:
return IDL.Int32;
case -12:
return IDL.Int64;
case -13:
return IDL.Float32;
case -14:
return IDL.Float64;
case -15:
return IDL.Text;
case -16:
return IDL.Reserved;
case -17:
return IDL.Empty;
case -24:
return IDL.Principal;
default:
throw 'Illegal op_code: t';
}
}
if (t >= rawTable.length) {
throw 'type index out of range';
}
return table[t];
}
ConstructType buildType(List<dynamic> entry) {
// entry: [IDLTypeIds, any]
switch (entry[0]) {
case IDLTypeIds.Vector:
{
var ty = getType(entry[1]);
return Vec(ty);
}
case IDLTypeIds.Opt:
{
var ty = getType(entry[1]);
return Opt(ty);
}
case IDLTypeIds.Record:
{
var fields = <String, CType>{};
for (var e in entry[1] as List) {
var name = "_${e[0].toString()}_";
fields[name] = getType(e[1] as int);
}
var record = Record(fields);
var tuple = record.tryAsTuple();
if (tuple is List<CType>) {
return Tuple(tuple);
} else {
return record;
}
}
case IDLTypeIds.Variant:
{
var fields = <String, CType>{};
for (var e in entry[1] as List) {
var name = "_${e[0]}_";
fields[name] = getType(e[1] as int);
}
return Variant(fields);
}
case IDLTypeIds.Func:
{
return Func([], [], []);
}
case IDLTypeIds.Service:
{
return Service({});
}
default:
throw 'Illegal op_code: ${entry[0]}';
}
}
rawTable.asMap().forEach((i, entry) {
final t = buildType(entry);
table[i].fill(t);
});
final types = rawTypes.map((t) => getType(t)).toList();
final output = retTypes.asMap().entries.map((entry) {
var result = entry.value.decodeValue(b, types[entry.key]);
return result;
}).toList();
// skip unused values
for (var ind = retTypes.length; ind < types.length; ind++) {
types[ind].decodeValue(b, types[ind]);
}
if (b.buffer.isNotEmpty) {
throw 'decode: Left-over bytes';
}
return output;
}