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