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 deserialized to JS type

Implementation

List idlDecode(List<CType> retTypes, Uint8List bytes) {
  final b = Pipe(bytes);
  if (bytes.lengthInBytes < _magicNumber.length) {
    throw RangeError.range(
      bytes.lengthInBytes,
      _magicNumber.length,
      null,
      'bytes',
      'Message length is smaller than the magic number',
    );
  }
  final magic = Uint8List.fromList(
    safeRead(b, _magicNumber.length),
  ).u8aToString();
  if (magic != _magicNumber) {
    throw StateError('Wrong magic number: $magic.');
  }

  // [Array<[IDLTypeIds, any]>, number[]]
  List<dynamic> readTypeTable(Pipe pipe) {
    // Array<[IDLTypeIds, any]>;
    final typeTable = [];
    final len = lebDecode(pipe).toInt();

    for (int i = 0; i < len; i++) {
      final ty = slebDecode(pipe).toInt();
      switch (ty) {
        case IDLTypeIds.Opt:
        case IDLTypeIds.Vector:
          final t = slebDecode(pipe).toInt();
          typeTable.add([ty, t]);
          break;
        case IDLTypeIds.Record:
        case IDLTypeIds.Variant:
          final fields = List.from([]);
          int objectLength = lebDecode(pipe).toInt();
          int? prevHash;
          while (objectLength-- > 0) {
            final hash = lebDecode(pipe).toInt();
            if (hash >= math.pow(2, 32)) {
              throw RangeError.range(
                hash,
                null,
                math.pow(2, 32).toInt(),
                'hash',
                'Field ID is out of 32-bit range',
              );
            }
            if (prevHash != null && prevHash >= hash) {
              throw StateError('Field ID collision or not sorted.');
            }
            prevHash = hash;
            final t = slebDecode(pipe).toInt();
            fields.add([hash, t]);
          }
          typeTable.add([ty, fields]);
          break;
        case IDLTypeIds.Func:
          final args = [];
          int argLength = lebDecode(pipe).toInt();
          while (argLength-- > 0) {
            args.add(slebDecode(pipe).toInt());
          }
          final returnValues = [];
          int returnValuesLength = lebDecode(pipe).toInt();
          while (returnValuesLength-- > 0) {
            returnValues.add(slebDecode(pipe).toInt());
          }
          final annotations = [];
          int annotationLength = lebDecode(pipe).toInt();
          while (annotationLength-- > 0) {
            final annotation = lebDecode(pipe).toInt();
            switch (annotation) {
              case 1:
                annotations.add('query');
                break;
              case 2:
                annotations.add('oneway');
                break;
              case 3:
                annotations.add('composite_query');
                break;
              default:
                throw ArgumentError('unknown annotation($annotation)');
            }
          }
          typeTable.add([
            ty,
            [args, returnValues, annotations],
          ]);
          break;
        case IDLTypeIds.Service:
          int servLength = lebDecode(pipe).toInt();
          while (servLength-- > 0) {
            final l = lebDecode(pipe).toInt();
            safeRead(pipe, l);
            slebDecode(pipe);
          }
          typeTable.add([ty, null]);
          break;
        default:
          throw UnreachableError();
      }
    }

    final rawList = <int>[];
    final length = lebDecode(pipe).toInt();
    for (int i = 0; i < length; i++) {
      rawList.add(slebDecode(pipe).toInt());
    }
    return [typeTable, rawList];
  }

  final typeTableRead = readTypeTable(b);

  final rawTable = typeTableRead[0] as List<dynamic>; // [IDLTypeIds, any]
  final rawTypes = typeTableRead[1] as List<int>;
  if (rawTypes.length < retTypes.length) {
    throw RangeError.range(
      rawTypes.length,
      retTypes.length,
      null,
      'rawTypes',
      'Wrong number of return values',
    );
  }

  final table = rawTable.map((_) => RecClass()).toList();

  CType getType(int t) {
    if (t < -24) {
      throw UnsupportedError('Future value is 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 StateError('Invalid type code $t.');
      }
    }
    if (t >= rawTable.length) {
      throw RangeError.range(
        t,
        rawTable.length,
        rawTable.length,
        'type',
        'Type is out of range',
      );
    }
    return table[t];
  }

  ConstructType buildType(List<dynamic> entry) {
    // entry: [IDLTypeIds, any]
    switch (entry[0]) {
      case IDLTypeIds.Vector:
        return Vec(getType(entry[1]));
      case IDLTypeIds.Opt:
        return Opt(getType(entry[1]));
      case IDLTypeIds.Record:
        final fields = <String, CType>{};
        for (final e in entry[1] as List) {
          final name = '_${e[0].toString()}_';
          fields[name] = getType(e[1] as int);
        }
        final record = Record(fields);
        final tuple = record.tryAsTuple();
        if (tuple is List<CType>) {
          return Tuple(tuple);
        } else {
          return record;
        }
      case IDLTypeIds.Variant:
        final fields = <String, CType>{};
        for (final e in entry[1] as List) {
          final name = '_${e[0]}_';
          fields[name] = getType(e[1] as int);
        }
        return Variant(fields);
      case IDLTypeIds.Func:
        return const Func([], [], []);
      case IDLTypeIds.Service:
        return Service();
      default:
        throw StateError('Invalid type 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) {
    final result = entry.value.decodeValue(b, types[entry.key]);
    return result;
  }).toList();

  // Skip unused values.
  for (int ind = retTypes.length; ind < types.length; ind++) {
    types[ind].decodeValue(b, types[ind]);
  }
  if (b.buffer.isNotEmpty) {
    throw StateError('Unexpected left-over bytes.');
  }
  return output;
}