From 1a62157502c42a80ad69eae5e258c4acfc859ff1 Mon Sep 17 00:00:00 2001 From: Ethan Celletti Date: Thu, 27 Apr 2023 10:50:23 -0700 Subject: [PATCH] Fixing value tuples (#72) --- src/Candid/Mapping/IResolvableTypeInfo.cs | 24 +++++++ src/Candid/Mapping/Mappers/TupleMapper.cs | 81 ++++++++++++++++++++++ src/Candid/Utilities/ByteUtil.cs | 4 +- src/EdjCase.Cryptography.BLS/IcpBlsUtil.cs | 47 +++++-------- 4 files changed, 125 insertions(+), 31 deletions(-) create mode 100644 src/Candid/Mapping/Mappers/TupleMapper.cs diff --git a/src/Candid/Mapping/IResolvableTypeInfo.cs b/src/Candid/Mapping/IResolvableTypeInfo.cs index f0dfc6ce..ef78499e 100644 --- a/src/Candid/Mapping/IResolvableTypeInfo.cs +++ b/src/Candid/Mapping/IResolvableTypeInfo.cs @@ -278,6 +278,10 @@ private static IResolvableTypeInfo BuildTypeInfo(Type objType) { return BuildOpt(objType); } + if (objType.Name.StartsWith("ValueTuple")) + { + return BuildTuple(objType, objType.GenericTypeArguments); + } if (genericTypeDefinition == typeof(List<>)) { Type innerType = objType.GenericTypeArguments[0]; @@ -462,6 +466,26 @@ private static IResolvableTypeInfo BuildVariant(Type objType, VariantAttribute a }); } + private static IResolvableTypeInfo BuildTuple(Type objType, Type[] innerTypes) + { + + return new ComplexTypeInfo(objType, innerTypes.ToList(), (resolvedMappings) => + { + List<(Type, CandidType)> tupleTypes = innerTypes + .Select(p => (p, resolvedMappings[p])) + .ToList(); + Dictionary fieldTypes = tupleTypes + .Select((t, i) => (Index: i, Type: t)) + .ToDictionary( + p => CandidTag.FromId((uint)p.Index), + p => p.Type.Item2 + ); + CandidRecordType type = new CandidRecordType(fieldTypes); + + return (new TupleMapper(objType, tupleTypes), type); + }); + } + private static IResolvableTypeInfo BuildRecord(Type objType) { List properties = objType diff --git a/src/Candid/Mapping/Mappers/TupleMapper.cs b/src/Candid/Mapping/Mappers/TupleMapper.cs new file mode 100644 index 00000000..3d7bb774 --- /dev/null +++ b/src/Candid/Mapping/Mappers/TupleMapper.cs @@ -0,0 +1,81 @@ +using EdjCase.ICP.Candid.Models.Types; +using EdjCase.ICP.Candid.Models.Values; +using EdjCase.ICP.Candid.Models; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace EdjCase.ICP.Candid.Mapping.Mappers +{ + + internal class TupleMapper : ICandidValueMapper + { + public CandidType CandidType { get; } + + public Type Type { get; } + public List Types { get; } + + public TupleMapper(Type type, List<(Type, CandidType)> tupleTypes) + { + Dictionary fields = tupleTypes + .Select((t, i) => (Index: i, Type: t.Item2)) + .ToDictionary(t => CandidTag.FromId((uint)t.Index), t => t.Type); + this.Type = type; + this.CandidType = new CandidRecordType(fields); + this.Types = tupleTypes.Select(t => t.Item1).ToList(); + } + + public object Map(CandidValue value, CandidConverter converter) + { + CandidRecord record = value.AsRecord(); + object obj = Activator.CreateInstance(this.Type); + static void SetValue(object tuple, int itemIndex, object? itemValue) + { + if (itemIndex >= 7) + { + // Index 7+ is located in the "Rest" which is a nested tuple + object restTuple = tuple.GetType().GetField("Rest").GetValue(tuple); + SetValue(restTuple, itemIndex - 7, itemValue); + } + else + { + tuple.GetType().GetField("Item" + (itemIndex + 1)).SetValue(tuple, itemValue); + } + } + uint i = 0; + foreach (Type fieldType in this.Types) + { + CandidTag fieldId = CandidTag.FromId(i); + if (!record.Fields.TryGetValue(fieldId, out CandidValue fieldCandidValue)) + { + throw new Exception($"Could not map candid record to type '{this.Type}'. Record is missing field '{fieldId}'"); + } + object? fieldValue = converter.ToObject(fieldType, fieldCandidValue); + SetValue(obj, (int)i, fieldValue); + i++; + } + return obj; + } + + public CandidValue Map(object value, CandidConverter converter) + { + ITuple tuple = (ITuple)value; + Dictionary fields = new(); + int i = 0; + foreach (Type type in this.Types) + { + object propValue = tuple[i]; + CandidValue v = converter.FromObject(propValue); + fields.Add(CandidTag.FromId((uint)i), v); + } + return new CandidRecord(fields); + } + + public CandidType? GetMappedCandidType(Type type) + { + return this.CandidType; + } + } +} diff --git a/src/Candid/Utilities/ByteUtil.cs b/src/Candid/Utilities/ByteUtil.cs index b5e3ac2f..5cb2f046 100644 --- a/src/Candid/Utilities/ByteUtil.cs +++ b/src/Candid/Utilities/ByteUtil.cs @@ -34,7 +34,9 @@ public static string ToHexString(this Span bytes) public static string ToHexString(this ReadOnlySpan bytes) { - Span stringValue = stackalloc char[bytes.Length * 2]; + Span stringValue = bytes.Length > 1_000 + ? new char[bytes.Length * 2] + : stackalloc char[bytes.Length * 2]; int i = 0; foreach (byte b in bytes) { diff --git a/src/EdjCase.Cryptography.BLS/IcpBlsUtil.cs b/src/EdjCase.Cryptography.BLS/IcpBlsUtil.cs index d29c4b21..a6f8c51e 100644 --- a/src/EdjCase.Cryptography.BLS/IcpBlsUtil.cs +++ b/src/EdjCase.Cryptography.BLS/IcpBlsUtil.cs @@ -13,7 +13,7 @@ public static class IcpBlsUtil private static object intializeLock = new object(); private static bool isInitialized = false; - private static DelegatesCache cache; + private static DelegatesCache? cache; /// /// Verifies a BLS signature (ICP flavor only) @@ -53,7 +53,7 @@ byte[] signature EnsureInitialized(); var blsPublicKey = default(Interop.PublicKey); - ulong publicKeyBytesRead = cache.publicKeyDeserialize(ref blsPublicKey, publicKey, (ulong)publicKey!.LongLength); + ulong publicKeyBytesRead = cache!.publicKeyDeserialize(ref blsPublicKey, publicKey, (ulong)publicKey!.LongLength); if (publicKeyBytesRead != (ulong)publicKey.Length) { @@ -166,37 +166,24 @@ public static DelegatesCache Create() Delegates.SignatureDeserialize signatureDeserialize; Delegates.Verify verify; Delegates.PublicKeySetHexStr publicKeySetHexStr; - if (true) - { - string libraryName = "bls384_256"; + const string libraryName = "bls384_256"; - IntPtr libraryHandle = NativeInterop.LoadNativeLibrary(libraryName); - T Get(string functionName) - { - IntPtr blsInitPtr = NativeInterop.GetFunctionPointer(libraryHandle, functionName); - return Marshal.GetDelegateForFunctionPointer(blsInitPtr); - } - init = Get("blsInit"); - setEthSerialization = Get("blsSetETHserialization"); - setMapToMode = Get("blsSetMapToMode"); - setGeneratorOfPublicKey = Get("blsSetGeneratorOfPublicKey"); - mclBnG1SetDst = Get("mclBnG1_setDst"); - publicKeyDeserialize = Get("blsPublicKeyDeserialize"); - signatureDeserialize = Get("blsSignatureDeserialize"); - verify = Get("blsVerify"); - publicKeySetHexStr = Get("blsPublicKeySetHexStr"); - } - else + IntPtr libraryHandle = NativeInterop.LoadNativeLibrary(libraryName); + T Get(string functionName) { - init = Interop.blsInit; - setEthSerialization = Interop.blsSetETHserialization; - setMapToMode = Interop.blsSetMapToMode; - setGeneratorOfPublicKey = Interop.blsSetGeneratorOfPublicKey; - mclBnG1SetDst = Interop.mclBnG1_setDst; - publicKeyDeserialize = Interop.blsPublicKeyDeserialize; - signatureDeserialize = Interop.blsSignatureDeserialize; - verify = Interop.blsVerify; + IntPtr blsInitPtr = NativeInterop.GetFunctionPointer(libraryHandle, functionName); + return Marshal.GetDelegateForFunctionPointer(blsInitPtr); } + init = Get("blsInit"); + setEthSerialization = Get("blsSetETHserialization"); + setMapToMode = Get("blsSetMapToMode"); + setGeneratorOfPublicKey = Get("blsSetGeneratorOfPublicKey"); + mclBnG1SetDst = Get("mclBnG1_setDst"); + publicKeyDeserialize = Get("blsPublicKeyDeserialize"); + signatureDeserialize = Get("blsSignatureDeserialize"); + verify = Get("blsVerify"); + publicKeySetHexStr = Get("blsPublicKeySetHexStr"); + return new DelegatesCache( init, setEthSerialization,