diff --git a/CryptoExchange.Net/Authentication/AuthenticationProvider.cs b/CryptoExchange.Net/Authentication/AuthenticationProvider.cs index 65551ba..e3517a7 100644 --- a/CryptoExchange.Net/Authentication/AuthenticationProvider.cs +++ b/CryptoExchange.Net/Authentication/AuthenticationProvider.cs @@ -74,6 +74,17 @@ namespace CryptoExchange.Net.Authentication return encryptor.ComputeHash(Encoding.UTF8.GetBytes(data)); } + /// + /// SHA256 sign the data and return the bytes + /// + /// + /// + protected static byte[] SignSHA256Bytes(byte[] data) + { + using var encryptor = SHA256.Create(); + return encryptor.ComputeHash(data); + } + /// /// SHA256 sign the data and return the hash /// @@ -87,6 +98,19 @@ namespace CryptoExchange.Net.Authentication return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes); } + /// + /// SHA256 sign the data and return the hash + /// + /// Data to sign + /// String type + /// + protected static string SignSHA256(byte[] data, SignOutputType? outputType = null) + { + using var encryptor = SHA256.Create(); + var resultBytes = encryptor.ComputeHash(data); + return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes); + } + /// /// SHA384 sign the data and return the hash /// @@ -100,6 +124,41 @@ namespace CryptoExchange.Net.Authentication return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes); } + /// + /// SHA384 sign the data and return the hash + /// + /// Data to sign + /// String type + /// + protected static string SignSHA384(byte[] data, SignOutputType? outputType = null) + { + using var encryptor = SHA384.Create(); + var resultBytes = encryptor.ComputeHash(data); + return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes); + } + + /// + /// SHA384 sign the data and return the hash + /// + /// Data to sign + /// + protected static byte[] SignSHA384Bytes(string data) + { + using var encryptor = SHA384.Create(); + return encryptor.ComputeHash(Encoding.UTF8.GetBytes(data)); + } + + /// + /// SHA384 sign the data and return the hash + /// + /// Data to sign + /// + protected static byte[] SignSHA384Bytes(byte[] data) + { + using var encryptor = SHA384.Create(); + return encryptor.ComputeHash(data); + } + /// /// SHA512 sign the data and return the hash /// @@ -113,6 +172,41 @@ namespace CryptoExchange.Net.Authentication return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes); } + /// + /// SHA512 sign the data and return the hash + /// + /// Data to sign + /// String type + /// + protected static string SignSHA512(byte[] data, SignOutputType? outputType = null) + { + using var encryptor = SHA512.Create(); + var resultBytes = encryptor.ComputeHash(data); + return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes); + } + + /// + /// SHA512 sign the data and return the hash + /// + /// Data to sign + /// + protected static byte[] SignSHA512Bytes(string data) + { + using var encryptor = SHA512.Create(); + return encryptor.ComputeHash(Encoding.UTF8.GetBytes(data)); + } + + /// + /// SHA512 sign the data and return the hash + /// + /// Data to sign + /// + protected static byte[] SignSHA512Bytes(byte[] data) + { + using var encryptor = SHA512.Create(); + return encryptor.ComputeHash(data); + } + /// /// MD5 sign the data and return the hash /// @@ -127,28 +221,70 @@ namespace CryptoExchange.Net.Authentication } /// - /// HMACSHA256 sign the data and return the hash + /// MD5 sign the data and return the hash + /// + /// Data to sign + /// String type + /// + protected static string SignMD5(byte[] data, SignOutputType? outputType = null) + { + using var encryptor = MD5.Create(); + var resultBytes = encryptor.ComputeHash(data); + return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes); + } + + /// + /// MD5 sign the data and return the hash + /// + /// Data to sign + /// + protected static byte[] SignMD5Bytes(string data) + { + using var encryptor = MD5.Create(); + return encryptor.ComputeHash(Encoding.UTF8.GetBytes(data)); + } + + /// + /// HMACSHA512 sign the data and return the hash /// /// Data to sign /// String type /// protected string SignHMACSHA256(string data, SignOutputType? outputType = null) + => SignHMACSHA256(Encoding.UTF8.GetBytes(data), outputType); + + /// + /// HMACSHA256 sign the data and return the hash + /// + /// Data to sign + /// String type + /// + protected string SignHMACSHA256(byte[] data, SignOutputType? outputType = null) { using var encryptor = new HMACSHA256(_sBytes); - var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data)); + var resultBytes = encryptor.ComputeHash(data); return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes); } + /// + /// HMACSHA512 sign the data and return the hash + /// + /// Data to sign + /// String type + /// + protected string SignHMACSHA384(string data, SignOutputType? outputType = null) + => SignHMACSHA384(Encoding.UTF8.GetBytes(data), outputType); + /// /// HMACSHA384 sign the data and return the hash /// /// Data to sign /// String type /// - protected string SignHMACSHA384(string data, SignOutputType? outputType = null) + protected string SignHMACSHA384(byte[] data, SignOutputType? outputType = null) { using var encryptor = new HMACSHA384(_sBytes); - var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data)); + var resultBytes = encryptor.ComputeHash(data); return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes); } @@ -182,7 +318,46 @@ namespace CryptoExchange.Net.Authentication /// protected string SignRSASHA256(byte[] data, SignOutputType? outputType = null) { - using var rsa = RSA.Create(); + using var rsa = CreateRSA(); + using var sha256 = SHA256.Create(); + var hash = sha256.ComputeHash(data); + var resultBytes = rsa.SignHash(hash, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + return outputType == SignOutputType.Base64? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes); + } + + /// + /// SHA384 sign the data + /// + /// + /// + /// + protected string SignRSASHA384(byte[] data, SignOutputType? outputType = null) + { + using var rsa = CreateRSA(); + using var sha384 = SHA384.Create(); + var hash = sha384.ComputeHash(data); + var resultBytes = rsa.SignHash(hash, HashAlgorithmName.SHA384, RSASignaturePadding.Pkcs1); + return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes); + } + + /// + /// SHA512 sign the data + /// + /// + /// + /// + protected string SignRSASHA512(byte[] data, SignOutputType? outputType = null) + { + using var rsa = CreateRSA(); + using var sha512 = SHA512.Create(); + var hash = sha512.ComputeHash(data); + var resultBytes = rsa.SignHash(hash, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1); + return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes); + } + + private RSA CreateRSA() + { + var rsa = RSA.Create(); if (_credentials.CredentialType == ApiCredentialsType.RsaPem) { #if NETSTANDARD2_1_OR_GREATER @@ -209,30 +384,7 @@ namespace CryptoExchange.Net.Authentication throw new Exception("Invalid credentials type"); } - using var sha256 = SHA256.Create(); - var hash = sha256.ComputeHash(data); - var resultBytes = rsa.SignHash(hash, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); - return outputType == SignOutputType.Base64? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes); - } - - /// - /// Sign a string - /// - /// - /// - public virtual string Sign(string toSign) - { - return toSign; - } - - /// - /// Sign a byte array - /// - /// - /// - public virtual byte[] Sign(byte[] toSign) - { - return toSign; + return rsa; } /// diff --git a/CryptoExchange.Net/Clients/BaseApiClient.cs b/CryptoExchange.Net/Clients/BaseApiClient.cs index dbdd27f..55072c7 100644 --- a/CryptoExchange.Net/Clients/BaseApiClient.cs +++ b/CryptoExchange.Net/Clients/BaseApiClient.cs @@ -7,6 +7,7 @@ using System.Net.Http; using System.Text; using System.Threading.Tasks; using CryptoExchange.Net.Authentication; +using CryptoExchange.Net.Converters; using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects.Options; @@ -78,13 +79,9 @@ namespace CryptoExchange.Net public bool OutputOriginalData { get; } /// - /// A default serializer + /// The default serializer /// - private static readonly JsonSerializer _defaultSerializer = JsonSerializer.Create(new JsonSerializerSettings - { - DateTimeZoneHandling = DateTimeZoneHandling.Utc, - Culture = CultureInfo.InvariantCulture - }); + protected virtual JsonSerializer DefaultSerializer { get; set; } = JsonSerializer.Create(SerializerOptions.Default); /// /// Api options @@ -204,7 +201,7 @@ namespace CryptoExchange.Net /// protected CallResult Deserialize(JToken obj, JsonSerializer? serializer = null, int? requestId = null) { - serializer ??= _defaultSerializer; + serializer ??= DefaultSerializer; try { @@ -242,7 +239,7 @@ namespace CryptoExchange.Net /// protected async Task> DeserializeAsync(Stream stream, JsonSerializer? serializer = null, int? requestId = null, long? elapsedMilliseconds = null) { - serializer ??= _defaultSerializer; + serializer ??= DefaultSerializer; string? data = null; try diff --git a/CryptoExchange.Net/Clients/RestApiClient.cs b/CryptoExchange.Net/Clients/RestApiClient.cs index 6b894c5..769c381 100644 --- a/CryptoExchange.Net/Clients/RestApiClient.cs +++ b/CryptoExchange.Net/Clients/RestApiClient.cs @@ -273,7 +273,7 @@ namespace CryptoExchange.Net if (response.IsSuccessStatusCode) { // If we have to manually parse error responses (can't rely on HttpStatusCode) we'll need to read the full - // response before being able to deserialize it into the resulting type since we don't know if it an error response or data + // response before being able to deserialize it into the resulting type since we don't know if its an error response or data if (manualParseError) { using var reader = new StreamReader(responseStream); diff --git a/CryptoExchange.Net/Converters/BoolConverter.cs b/CryptoExchange.Net/Converters/BoolConverter.cs index fb8431f..92b679e 100644 --- a/CryptoExchange.Net/Converters/BoolConverter.cs +++ b/CryptoExchange.Net/Converters/BoolConverter.cs @@ -47,6 +47,7 @@ namespace CryptoExchange.Net.Converters case "n": case "0": case "off": + case "-1": return false; } diff --git a/CryptoExchange.Net/Converters/EnumConverter.cs b/CryptoExchange.Net/Converters/EnumConverter.cs index 1da595a..9188c57 100644 --- a/CryptoExchange.Net/Converters/EnumConverter.cs +++ b/CryptoExchange.Net/Converters/EnumConverter.cs @@ -33,7 +33,7 @@ namespace CryptoExchange.Net.Converters /// public override bool CanConvert(Type objectType) { - return objectType.IsEnum; + return objectType.IsEnum || Nullable.GetUnderlyingType(objectType)?.IsEnum == true; } /// diff --git a/CryptoExchange.Net/Converters/SerializerOptions.cs b/CryptoExchange.Net/Converters/SerializerOptions.cs new file mode 100644 index 0000000..2e71a5d --- /dev/null +++ b/CryptoExchange.Net/Converters/SerializerOptions.cs @@ -0,0 +1,35 @@ +using Newtonsoft.Json; +using System.Globalization; + +namespace CryptoExchange.Net.Converters +{ + /// + /// Serializer options + /// + public static class SerializerOptions + { + /// + /// Json serializer settings which includes the EnumConverter, DateTimeConverter and BoolConverter + /// + public static JsonSerializerSettings WithConverters => new JsonSerializerSettings + { + DateTimeZoneHandling = DateTimeZoneHandling.Utc, + Culture = CultureInfo.InvariantCulture, + Converters = + { + new EnumConverter(), + new DateTimeConverter(), + new BoolConverter() + } + }; + + /// + /// Default json serializer settings + /// + public static JsonSerializerSettings Default => new JsonSerializerSettings + { + DateTimeZoneHandling = DateTimeZoneHandling.Utc, + Culture = CultureInfo.InvariantCulture + }; + } +} diff --git a/CryptoExchange.Net/CryptoExchange.Net.csproj b/CryptoExchange.Net/CryptoExchange.Net.csproj index 6451da5..16a8b04 100644 --- a/CryptoExchange.Net/CryptoExchange.Net.csproj +++ b/CryptoExchange.Net/CryptoExchange.Net.csproj @@ -1,4 +1,4 @@ - + netstandard2.0;netstandard2.1 diff --git a/CryptoExchange.Net/Objects/ParameterCollection.cs b/CryptoExchange.Net/Objects/ParameterCollection.cs new file mode 100644 index 0000000..ffcad33 --- /dev/null +++ b/CryptoExchange.Net/Objects/ParameterCollection.cs @@ -0,0 +1,172 @@ +using CryptoExchange.Net.Attributes; +using CryptoExchange.Net.Converters; +using System; +using System.Collections.Generic; +using System.Globalization; + +namespace CryptoExchange.Net.Objects +{ + /// + /// Parameters collection + /// + public class ParameterCollection : Dictionary + { + /// + /// Add an optional parameter. Not added if value is null + /// + /// + /// + public void AddOptional(string key, object? value) + { + if (value != null) + Add(key, value); + } + + /// + /// Add a decimal value as string + /// + /// + /// + public void AddString(string key, decimal value) + { + Add(key, value.ToString(CultureInfo.InvariantCulture)); + } + + /// + /// Add a decimal value as string. Not added if value is null + /// + /// + /// + public void AddOptionalString(string key, decimal? value) + { + if (value != null) + Add(key, value.Value.ToString(CultureInfo.InvariantCulture)); + } + + /// + /// Add a int value as string + /// + /// + /// + public void AddString(string key, int value) + { + Add(key, value.ToString(CultureInfo.InvariantCulture)); + } + + /// + /// Add a int value as string. Not added if value is null + /// + /// + /// + public void AddOptionalString(string key, int? value) + { + if (value != null) + Add(key, value.Value.ToString(CultureInfo.InvariantCulture)); + } + + /// + /// Add a long value as string + /// + /// + /// + public void AddString(string key, long value) + { + Add(key, value.ToString(CultureInfo.InvariantCulture)); + } + + /// + /// Add a long value as string. Not added if value is null + /// + /// + /// + public void AddOptionalString(string key, long? value) + { + if (value != null) + Add(key, value.Value.ToString(CultureInfo.InvariantCulture)); + } + + /// + /// Add a datetime value as milliseconds timestamp + /// + /// + /// + public void AddMilliseconds(string key, DateTime value) + { + Add(key, DateTimeConverter.ConvertToMilliseconds(value)); + } + + /// + /// Add a datetime value as milliseconds timestamp. Not added if value is null + /// + /// + /// + public void AddOptionalMilliseconds(string key, DateTime? value) + { + if (value != null) + Add(key, DateTimeConverter.ConvertToMilliseconds(value)); + } + + /// + /// Add a datetime value as milliseconds timestamp + /// + /// + /// + public void AddMillisecondsString(string key, DateTime value) + { + Add(key, DateTimeConverter.ConvertToMilliseconds(value).Value.ToString(CultureInfo.InvariantCulture)); + } + + /// + /// Add a datetime value as milliseconds timestamp. Not added if value is null + /// + /// + /// + public void AddOptionalMillisecondsString(string key, DateTime? value) + { + if (value != null) + Add(key, DateTimeConverter.ConvertToMilliseconds(value).Value.ToString(CultureInfo.InvariantCulture)); + } + + /// + /// Add a datetime value as seconds timestamp + /// + /// + /// + public void AddSeconds(string key, DateTime value) + { + Add(key, DateTimeConverter.ConvertToSeconds(value)); + } + + /// + /// Add a datetime value as seconds timestamp. Not added if value is null + /// + /// + /// + public void AddOptionalSeconds(string key, DateTime? value) + { + if (value != null) + Add(key, DateTimeConverter.ConvertToSeconds(value)); + } + + /// + /// Add an enum value as the string value as mapped using the + /// + /// + /// + public void AddEnum(string key, T value) + { + Add(key, EnumConverter.GetString(value)!); + } + + /// + /// Add an enum value as the string value as mapped using the . Not added if value is null + /// + /// + /// + public void AddOptionalEnum(string key, T? value) + { + if (value != null) + Add(key, EnumConverter.GetString(value)); + } + } +}