idlDecode function

List idlDecode(
  1. List<CType> retTypes,
  2. Uint8List bytes
)

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;
}