From 949780a9addc34a08c8d0e428f7b1b589f4b4a00 Mon Sep 17 00:00:00 2001 From: JKorf Date: Thu, 1 Aug 2024 22:43:06 +0200 Subject: [PATCH] Removed SecureString usage throughout the library, removed some object allocations, removed some unused extension methods --- CryptoExchange.Net.UnitTests/OptionsTests.cs | 12 +- .../RestClientTests.cs | 32 +++--- .../TestImplementations/TestBaseClient.cs | 4 +- .../Authentication/ApiCredentials.cs | 52 ++------- .../Authentication/AuthenticationProvider.cs | 25 ++--- CryptoExchange.Net/Clients/BaseApiClient.cs | 7 -- CryptoExchange.Net/Clients/RestApiClient.cs | 45 ++++---- CryptoExchange.Net/ExchangeHelpers.cs | 20 +--- CryptoExchange.Net/ExtensionMethods.cs | 106 ------------------ CryptoExchange.Net/Interfaces/IRateLimiter.cs | 2 +- CryptoExchange.Net/Objects/ApiProxy.cs | 17 +-- .../Filters/AuthenticatedEndpointFilter.cs | 2 +- .../RateLimiting/Filters/ExactPathFilter.cs | 2 +- .../RateLimiting/Filters/ExactPathsFilter.cs | 2 +- .../RateLimiting/Filters/HostFilter.cs | 2 +- .../Filters/LimitItemTypeFilter.cs | 2 +- .../RateLimiting/Filters/PathStartFilter.cs | 2 +- .../RateLimiting/Guards/RateLimitGuard.cs | 18 +-- .../RateLimiting/Guards/RetryAfterGuard.cs | 4 +- .../RateLimiting/Guards/SingleLimitGuard.cs | 12 +- .../RateLimiting/Interfaces/IGuardFilter.cs | 2 +- .../RateLimiting/Interfaces/IRateLimitGate.cs | 4 +- .../Interfaces/IRateLimitGuard.cs | 4 +- .../RateLimiting/RateLimitGate.cs | 8 +- CryptoExchange.Net/Testing/TestHelpers.cs | 6 +- 25 files changed, 111 insertions(+), 281 deletions(-) diff --git a/CryptoExchange.Net.UnitTests/OptionsTests.cs b/CryptoExchange.Net.UnitTests/OptionsTests.cs index 90c832d..8e60710 100644 --- a/CryptoExchange.Net.UnitTests/OptionsTests.cs +++ b/CryptoExchange.Net.UnitTests/OptionsTests.cs @@ -51,8 +51,8 @@ namespace CryptoExchange.Net.UnitTests // assert Assert.That(options.ReceiveWindow == TimeSpan.FromSeconds(10)); - Assert.That(options.ApiCredentials.Key.GetString() == "123"); - Assert.That(options.ApiCredentials.Secret.GetString() == "456"); + Assert.That(options.ApiCredentials.Key == "123"); + Assert.That(options.ApiCredentials.Secret == "456"); } [Test] @@ -64,10 +64,10 @@ namespace CryptoExchange.Net.UnitTests options.Api2Options.ApiCredentials = new ApiCredentials("789", "101"); // assert - Assert.That(options.Api1Options.ApiCredentials.Key.GetString() == "123"); - Assert.That(options.Api1Options.ApiCredentials.Secret.GetString() == "456"); - Assert.That(options.Api2Options.ApiCredentials.Key.GetString() == "789"); - Assert.That(options.Api2Options.ApiCredentials.Secret.GetString() == "101"); + Assert.That(options.Api1Options.ApiCredentials.Key == "123"); + Assert.That(options.Api1Options.ApiCredentials.Secret == "456"); + Assert.That(options.Api2Options.ApiCredentials.Key == "789"); + Assert.That(options.Api2Options.ApiCredentials.Secret == "101"); } [Test] diff --git a/CryptoExchange.Net.UnitTests/RestClientTests.cs b/CryptoExchange.Net.UnitTests/RestClientTests.cs index 1371039..ced33e7 100644 --- a/CryptoExchange.Net.UnitTests/RestClientTests.cs +++ b/CryptoExchange.Net.UnitTests/RestClientTests.cs @@ -176,12 +176,12 @@ namespace CryptoExchange.Net.UnitTests for (var i = 0; i < requests + 1; i++) { - var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123".ToSecureString(), 1, RateLimitingBehaviour.Wait, default); + var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, default); Assert.That(i == requests? triggered : !triggered); } triggered = false; await Task.Delay((int)Math.Round(perSeconds * 1000) + 10); - var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123".ToSecureString(), 1, RateLimitingBehaviour.Wait, default); + var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, default); Assert.That(!triggered); } @@ -201,7 +201,7 @@ namespace CryptoExchange.Net.UnitTests rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; for (var i = 0; i < 2; i++) { - var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123".ToSecureString(), 1, RateLimitingBehaviour.Wait, default); + var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, default); bool expected = i == 1 ? (expectLimiting ? evnt.DelayTime > TimeSpan.Zero : evnt == null) : evnt == null; Assert.That(expected); } @@ -222,9 +222,9 @@ namespace CryptoExchange.Net.UnitTests RateLimitEvent evnt = null; rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; - var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, "https://test.com", "123".ToSecureString(), 1, RateLimitingBehaviour.Wait, default); + var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, default); Assert.That(evnt == null); - var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition2, "https://test.com", "123".ToSecureString(), 1, RateLimitingBehaviour.Wait, default); + var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition2, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, default); Assert.That(expectLimiting ? evnt != null : evnt == null); } @@ -243,12 +243,12 @@ namespace CryptoExchange.Net.UnitTests for (var i = 0; i < requests + 1; i++) { - var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123".ToSecureString(), 1, RateLimitingBehaviour.Wait, default); + var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, default); Assert.That(i == requests ? triggered : !triggered); } triggered = false; await Task.Delay((int)Math.Round(perSeconds * 1000) + 10); - var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123".ToSecureString(), 1, RateLimitingBehaviour.Wait, default); + var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, default); Assert.That(!triggered); } @@ -266,7 +266,7 @@ namespace CryptoExchange.Net.UnitTests rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; for (var i = 0; i < 2; i++) { - var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123".ToSecureString(), 1, RateLimitingBehaviour.Wait, default); + var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, default); bool expected = i == 1 ? (expectLimited ? evnt.DelayTime > TimeSpan.Zero : evnt == null) : evnt == null; Assert.That(expected); } @@ -286,7 +286,7 @@ namespace CryptoExchange.Net.UnitTests rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; for (var i = 0; i < 2; i++) { - var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123".ToSecureString(), 1, RateLimitingBehaviour.Wait, default); + var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, default); bool expected = i == 1 ? (expectLimited ? evnt.DelayTime > TimeSpan.Zero : evnt == null) : evnt == null; Assert.That(expected); } @@ -309,9 +309,9 @@ namespace CryptoExchange.Net.UnitTests RateLimitEvent evnt = null; rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; - var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, "https://test.com", key1?.ToSecureString(), 1, RateLimitingBehaviour.Wait, default); + var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, "https://test.com", key1, 1, RateLimitingBehaviour.Wait, default); Assert.That(evnt == null); - var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition2, "https://test.com", key2?.ToSecureString(), 1, RateLimitingBehaviour.Wait, default); + var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition2, "https://test.com", key2, 1, RateLimitingBehaviour.Wait, default); Assert.That(expectLimited ? evnt != null : evnt == null); } @@ -328,7 +328,7 @@ namespace CryptoExchange.Net.UnitTests RateLimitEvent evnt = null; rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; - var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, "https://test.com", "123".ToSecureString(), 1, RateLimitingBehaviour.Wait, default); + var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, default); Assert.That(evnt == null); var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition2, "https://test.com", null, 1, RateLimitingBehaviour.Wait, default); Assert.That(expectLimited ? evnt != null : evnt == null); @@ -348,9 +348,9 @@ namespace CryptoExchange.Net.UnitTests RateLimitEvent evnt = null; rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; - var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, host1, "123".ToSecureString(), 1, RateLimitingBehaviour.Wait, default); + var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, host1, "123", 1, RateLimitingBehaviour.Wait, default); Assert.That(evnt == null); - var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, host2, "123".ToSecureString(), 1, RateLimitingBehaviour.Wait, default); + var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, host2, "123", 1, RateLimitingBehaviour.Wait, default); Assert.That(expectLimited ? evnt != null : evnt == null); } @@ -365,9 +365,9 @@ namespace CryptoExchange.Net.UnitTests RateLimitEvent evnt = null; rateLimiter.RateLimitTriggered += (x) => { evnt = x; }; - var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition("1", HttpMethod.Get), host1, "123".ToSecureString(), 1, RateLimitingBehaviour.Wait, default); + var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition("1", HttpMethod.Get), host1, "123", 1, RateLimitingBehaviour.Wait, default); Assert.That(evnt == null); - var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition("1", HttpMethod.Get), host2, "123".ToSecureString(), 1, RateLimitingBehaviour.Wait, default); + var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition("1", HttpMethod.Get), host2, "123", 1, RateLimitingBehaviour.Wait, default); Assert.That(expectLimited ? evnt != null : evnt == null); } } diff --git a/CryptoExchange.Net.UnitTests/TestImplementations/TestBaseClient.cs b/CryptoExchange.Net.UnitTests/TestImplementations/TestBaseClient.cs index 4a2e2f5..5f82106 100644 --- a/CryptoExchange.Net.UnitTests/TestImplementations/TestBaseClient.cs +++ b/CryptoExchange.Net.UnitTests/TestImplementations/TestBaseClient.cs @@ -72,7 +72,7 @@ namespace CryptoExchange.Net.UnitTests { } - public string GetKey() => _credentials.Key.GetString(); - public string GetSecret() => _credentials.Secret.GetString(); + public string GetKey() => _credentials.Key; + public string GetSecret() => _credentials.Secret; } } diff --git a/CryptoExchange.Net/Authentication/ApiCredentials.cs b/CryptoExchange.Net/Authentication/ApiCredentials.cs index b942853..eb7fb50 100644 --- a/CryptoExchange.Net/Authentication/ApiCredentials.cs +++ b/CryptoExchange.Net/Authentication/ApiCredentials.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using System.Security; using CryptoExchange.Net.Converters.SystemTextJson; using CryptoExchange.Net.Converters.MessageParsing; @@ -9,48 +8,23 @@ namespace CryptoExchange.Net.Authentication /// /// Api credentials, used to sign requests accessing private endpoints /// - public class ApiCredentials: IDisposable + public class ApiCredentials { /// /// The api key to authenticate requests /// - public SecureString? Key { get; } + public string Key { get; } /// /// The api secret to authenticate requests /// - public SecureString? Secret { get; } + public string Secret { get; } /// /// Type of the credentials /// public ApiCredentialsType CredentialType { get; } - /// - /// Create Api credentials providing an api key and secret for authentication - /// - /// The api key used for identification - /// The api secret used for signing - public ApiCredentials(SecureString key, SecureString secret) : this(key, secret, ApiCredentialsType.Hmac) - { - } - - /// - /// Create Api credentials providing an api key and secret for authentication - /// - /// The api key used for identification - /// The api secret used for signing - /// The type of credentials - public ApiCredentials(SecureString key, SecureString secret, ApiCredentialsType credentialsType) - { - if (key == null || secret == null) - throw new ArgumentException("Key and secret can't be null/empty"); - - CredentialType = credentialsType; - Key = key; - Secret = secret; - } - /// /// Create Api credentials providing an api key and secret for authentication /// @@ -72,8 +46,8 @@ namespace CryptoExchange.Net.Authentication throw new ArgumentException("Key and secret can't be null/empty"); CredentialType = credentialsType; - Key = key.ToSecureString(); - Secret = secret.ToSecureString(); + Key = key; + Secret = secret; } /// @@ -82,8 +56,7 @@ namespace CryptoExchange.Net.Authentication /// public virtual ApiCredentials Copy() { - // Use .GetString() to create a copy of the SecureString - return new ApiCredentials(Key!.GetString(), Secret!.GetString(), CredentialType); + return new ApiCredentials(Key, Secret, CredentialType); } /// @@ -103,19 +76,10 @@ namespace CryptoExchange.Net.Authentication if (key == null || secret == null) throw new ArgumentException("apiKey or apiSecret value not found in Json credential file"); - Key = key.ToSecureString(); - Secret = secret.ToSecureString(); + Key = key; + Secret = secret; inputStream.Seek(0, SeekOrigin.Begin); } - - /// - /// Dispose - /// - public void Dispose() - { - Key?.Dispose(); - Secret?.Dispose(); - } } } diff --git a/CryptoExchange.Net/Authentication/AuthenticationProvider.cs b/CryptoExchange.Net/Authentication/AuthenticationProvider.cs index 55e9d80..a3ad2c0 100644 --- a/CryptoExchange.Net/Authentication/AuthenticationProvider.cs +++ b/CryptoExchange.Net/Authentication/AuthenticationProvider.cs @@ -14,7 +14,7 @@ namespace CryptoExchange.Net.Authentication /// /// Base class for authentication providers /// - public abstract class AuthenticationProvider : IDisposable + public abstract class AuthenticationProvider { internal IAuthTimeProvider TimeProvider { get; set; } = new AuthTimeProvider(); @@ -28,6 +28,11 @@ namespace CryptoExchange.Net.Authentication /// protected byte[] _sBytes; + /// + /// Get the API key of the current credentials + /// + public string ApiKey => _credentials.Key; + /// /// ctor /// @@ -38,7 +43,7 @@ namespace CryptoExchange.Net.Authentication throw new ArgumentException("ApiKey/Secret needed"); _credentials = credentials; - _sBytes = Encoding.UTF8.GetBytes(credentials.Secret.GetString()); + _sBytes = Encoding.UTF8.GetBytes(credentials.Secret); } /// @@ -58,9 +63,9 @@ namespace CryptoExchange.Net.Authentication RestApiClient apiClient, Uri uri, HttpMethod method, - IDictionary uriParameters, - IDictionary bodyParameters, - Dictionary headers, + ref IDictionary? uriParameters, + ref IDictionary? bodyParameters, + ref Dictionary? headers, bool auth, ArrayParametersSerialization arraySerialization, HttpMethodParameterPosition parameterPosition, @@ -366,7 +371,7 @@ namespace CryptoExchange.Net.Authentication { #if NETSTANDARD2_1_OR_GREATER // Read from pem private key - var key = _credentials.Secret!.GetString() + var key = _credentials.Secret! .Replace("\n", "") .Replace("-----BEGIN PRIVATE KEY-----", "") .Replace("-----END PRIVATE KEY-----", "") @@ -381,7 +386,7 @@ namespace CryptoExchange.Net.Authentication else if (_credentials.CredentialType == ApiCredentialsType.RsaXml) { // Read from xml private key format - rsa.FromXmlString(_credentials.Secret!.GetString()); + rsa.FromXmlString(_credentials.Secret!); } else { @@ -447,12 +452,6 @@ namespace CryptoExchange.Net.Authentication else return serializer.Serialize(parameters); } - - /// - public void Dispose() - { - _credentials?.Dispose(); - } } /// diff --git a/CryptoExchange.Net/Clients/BaseApiClient.cs b/CryptoExchange.Net/Clients/BaseApiClient.cs index a377fbd..8ac98d0 100644 --- a/CryptoExchange.Net/Clients/BaseApiClient.cs +++ b/CryptoExchange.Net/Clients/BaseApiClient.cs @@ -65,10 +65,7 @@ namespace CryptoExchange.Net.Clients BaseAddress = baseAddress; if (apiCredentials != null) - { - AuthenticationProvider?.Dispose(); AuthenticationProvider = CreateAuthenticationProvider(apiCredentials.Copy()); - } } /// @@ -85,10 +82,7 @@ namespace CryptoExchange.Net.Clients public void SetApiCredentials(T credentials) where T : ApiCredentials { if (credentials != null) - { - AuthenticationProvider?.Dispose(); AuthenticationProvider = CreateAuthenticationProvider(credentials.Copy()); - } } /// @@ -97,7 +91,6 @@ namespace CryptoExchange.Net.Clients public virtual void Dispose() { _disposing = true; - AuthenticationProvider?.Dispose(); } } } diff --git a/CryptoExchange.Net/Clients/RestApiClient.cs b/CryptoExchange.Net/Clients/RestApiClient.cs index 60eb317..0221bbc 100644 --- a/CryptoExchange.Net/Clients/RestApiClient.cs +++ b/CryptoExchange.Net/Clients/RestApiClient.cs @@ -196,19 +196,20 @@ namespace CryptoExchange.Net.Clients Dictionary? additionalHeaders = null, int? weight = null) where T : class { - var key = baseAddress + definition + uriParameters?.ToFormData(); + string? cacheKey = null; if (ShouldCache(definition)) { - _logger.CheckingCache(key); - var cachedValue = _cache.Get(key, ClientOptions.CachingMaxAge); + cacheKey = baseAddress + definition + uriParameters?.ToFormData(); + _logger.CheckingCache(cacheKey); + var cachedValue = _cache.Get(cacheKey, ClientOptions.CachingMaxAge); if (cachedValue != null) { - _logger.CacheHit(key); + _logger.CacheHit(cacheKey); var original = (WebCallResult)cachedValue; return original.Cached(); } - _logger.CacheNotHit(key); + _logger.CacheNotHit(cacheKey); } int currentTry = 0; @@ -242,7 +243,7 @@ namespace CryptoExchange.Net.Clients if (result.Success && ShouldCache(definition)) { - _cache.Add(key, result); + _cache.Add(cacheKey!, result); } return result; @@ -343,15 +344,15 @@ namespace CryptoExchange.Net.Clients ParameterCollection? bodyParameters, Dictionary? additionalHeaders) { - var uriParams = uriParameters == null ? new ParameterCollection() : CreateParameterDictionary(uriParameters); - var bodyParams = bodyParameters == null ? new ParameterCollection() : CreateParameterDictionary(bodyParameters); + var uriParams = uriParameters == null ? null : CreateParameterDictionary(uriParameters); + var bodyParams = bodyParameters == null ? null : CreateParameterDictionary(bodyParameters); var uri = new Uri(baseAddress.AppendPath(definition.Path)); var arraySerialization = definition.ArraySerialization ?? ArraySerialization; var bodyFormat = definition.RequestBodyFormat ?? RequestBodyFormat; var parameterPosition = definition.ParameterPosition ?? ParameterPositions[definition.Method]; - var headers = new Dictionary(); + Dictionary? headers = null; if (AuthenticationProvider != null) { try @@ -360,9 +361,9 @@ namespace CryptoExchange.Net.Clients this, uri, definition.Method, - uriParams, - bodyParams, - headers, + ref uriParams, + ref bodyParams, + ref headers, definition.Authenticated, arraySerialization, parameterPosition, @@ -375,14 +376,18 @@ namespace CryptoExchange.Net.Clients } } - // Add the auth parameters to the uri, start with a new URI to be able to sort the parameters including the auth parameters - uri = uri.SetParameters(uriParams, arraySerialization); + // Add the auth parameters to the uri, start with a new URI to be able to sort the parameters including the auth parameters + if (uriParams != null) + uri = uri.SetParameters(uriParams, arraySerialization); var request = RequestFactory.Create(definition.Method, uri, requestId); request.Accept = Constants.JsonContentHeader; - foreach (var header in headers) - request.AddHeader(header.Key, header.Value); + if (headers != null) + { + foreach (var header in headers) + request.AddHeader(header.Key, header.Value); + } if (additionalHeaders != null) { @@ -403,7 +408,7 @@ namespace CryptoExchange.Net.Clients if (parameterPosition == HttpMethodParameterPosition.InBody) { var contentType = bodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader; - if (bodyParams.Count != 0) + if (bodyParams != null && bodyParams.Count != 0) WriteParamBody(request, bodyParams, contentType); else request.SetContent(RequestBodyEmptyContent, contentType); @@ -817,9 +822,9 @@ namespace CryptoExchange.Net.Clients this, uri, method, - uriParameters, - bodyParameters, - headers, + ref uriParameters, + ref bodyParameters, + ref headers, signed, arraySerialization, parameterPosition, diff --git a/CryptoExchange.Net/ExchangeHelpers.cs b/CryptoExchange.Net/ExchangeHelpers.cs index 20c759f..20049e2 100644 --- a/CryptoExchange.Net/ExchangeHelpers.cs +++ b/CryptoExchange.Net/ExchangeHelpers.cs @@ -1,6 +1,7 @@ using CryptoExchange.Net.Objects; using System; using System.Security.Cryptography; +using System.Threading; namespace CryptoExchange.Net { @@ -15,10 +16,6 @@ namespace CryptoExchange.Net /// The last used id, use NextId() to get the next id and up this /// private static int _lastId; - /// - /// Lock for id generating - /// - private static object _idLock = new(); /// /// Clamp a value between a min and max @@ -135,24 +132,13 @@ namespace CryptoExchange.Net /// Generate a new unique id. The id is staticly stored so it is guarenteed to be unique /// /// - public static int NextId() - { - lock (_idLock) - { - _lastId += 1; - return _lastId; - } - } + public static int NextId() => Interlocked.Increment(ref _lastId); /// /// Return the last unique id that was generated /// /// - public static int LastId() - { - lock (_idLock) - return _lastId; - } + public static int LastId() => _lastId; /// /// Generate a random string of specified length diff --git a/CryptoExchange.Net/ExtensionMethods.cs b/CryptoExchange.Net/ExtensionMethods.cs index 5fd22b1..c9e026b 100644 --- a/CryptoExchange.Net/ExtensionMethods.cs +++ b/CryptoExchange.Net/ExtensionMethods.cs @@ -113,92 +113,6 @@ namespace CryptoExchange.Net return formData.ToString(); } - /// - /// Get the string the secure string is representing - /// - /// The source secure string - /// - public static string GetString(this SecureString source) - { - lock (source) - { - string result; - var length = source.Length; - var pointer = IntPtr.Zero; - var chars = new char[length]; - - try - { - pointer = Marshal.SecureStringToBSTR(source); - Marshal.Copy(pointer, chars, 0, length); - - result = string.Join("", chars); - } - finally - { - if (pointer != IntPtr.Zero) - { - Marshal.ZeroFreeBSTR(pointer); - } - } - - return result; - } - } - - /// - /// Are 2 secure strings equal - /// - /// Source secure string - /// Compare secure string - /// True if equal by value - public static bool IsEqualTo(this SecureString ss1, SecureString ss2) - { - IntPtr bstr1 = IntPtr.Zero; - IntPtr bstr2 = IntPtr.Zero; - try - { - bstr1 = Marshal.SecureStringToBSTR(ss1); - bstr2 = Marshal.SecureStringToBSTR(ss2); - int length1 = Marshal.ReadInt32(bstr1, -4); - int length2 = Marshal.ReadInt32(bstr2, -4); - if (length1 == length2) - { - for (int x = 0; x < length1; ++x) - { - byte b1 = Marshal.ReadByte(bstr1, x); - byte b2 = Marshal.ReadByte(bstr2, x); - if (b1 != b2) return false; - } - } - else - { - return false; - } - - return true; - } - finally - { - if (bstr2 != IntPtr.Zero) Marshal.ZeroFreeBSTR(bstr2); - if (bstr1 != IntPtr.Zero) Marshal.ZeroFreeBSTR(bstr1); - } - } - - /// - /// Create a secure string from a string - /// - /// - /// - public static SecureString ToSecureString(this string source) - { - var secureString = new SecureString(); - foreach (var c in source) - secureString.AppendChar(c); - secureString.MakeReadOnly(); - return secureString; - } - /// /// Validates an int is one of the allowed values /// @@ -318,26 +232,6 @@ namespace CryptoExchange.Net return url.TrimEnd('/'); } - /// - /// Fill parameters in a path. Parameters are specified by '{}' and should be specified in occuring sequence - /// - /// The total path string - /// The values to fill - /// - public static string FillPathParameters(this string path, params string[] values) - { - foreach (var value in values) - { - var index = path.IndexOf("{}", StringComparison.Ordinal); - if (index >= 0) - { - path = path.Remove(index, 2); - path = path.Insert(index, value); - } - } - return path; - } - /// /// Create a new uri with the provided parameters as query /// diff --git a/CryptoExchange.Net/Interfaces/IRateLimiter.cs b/CryptoExchange.Net/Interfaces/IRateLimiter.cs index 8cfffea..eee90da 100644 --- a/CryptoExchange.Net/Interfaces/IRateLimiter.cs +++ b/CryptoExchange.Net/Interfaces/IRateLimiter.cs @@ -24,6 +24,6 @@ namespace CryptoExchange.Net.Interfaces /// The weight of the request /// Cancellation token to cancel waiting /// The time in milliseconds spend waiting - Task> LimitRequestAsync(ILogger log, string endpoint, HttpMethod method, bool signed, SecureString? apiKey, RateLimitingBehaviour limitBehaviour, int requestWeight, CancellationToken ct); + Task> LimitRequestAsync(ILogger log, string endpoint, HttpMethod method, bool signed, string? apiKey, RateLimitingBehaviour limitBehaviour, int requestWeight, CancellationToken ct); } } diff --git a/CryptoExchange.Net/Objects/ApiProxy.cs b/CryptoExchange.Net/Objects/ApiProxy.cs index 1e75156..08fc51e 100644 --- a/CryptoExchange.Net/Objects/ApiProxy.cs +++ b/CryptoExchange.Net/Objects/ApiProxy.cs @@ -24,14 +24,14 @@ namespace CryptoExchange.Net.Objects /// /// The password of the proxy /// - public SecureString? Password { get; } + public string? Password { get; } /// /// Create new settings for a proxy /// /// The proxy hostname/ip /// The proxy port - public ApiProxy(string host, int port): this(host, port, null, (SecureString?)null) + public ApiProxy(string host, int port): this(host, port, null, null) { } @@ -42,18 +42,7 @@ namespace CryptoExchange.Net.Objects /// The proxy port /// The proxy login /// The proxy password - public ApiProxy(string host, int port, string? login, string? password) : this(host, port, login, password?.ToSecureString()) - { - } - - /// - /// Create new settings for a proxy - /// - /// The proxy hostname/ip - /// The proxy port - /// The proxy login - /// The proxy password - public ApiProxy(string host, int port, string? login, SecureString? password) + public ApiProxy(string host, int port, string? login, string? password) { Host = host; Port = port; diff --git a/CryptoExchange.Net/RateLimiting/Filters/AuthenticatedEndpointFilter.cs b/CryptoExchange.Net/RateLimiting/Filters/AuthenticatedEndpointFilter.cs index ac03e22..22808d9 100644 --- a/CryptoExchange.Net/RateLimiting/Filters/AuthenticatedEndpointFilter.cs +++ b/CryptoExchange.Net/RateLimiting/Filters/AuthenticatedEndpointFilter.cs @@ -21,7 +21,7 @@ namespace CryptoExchange.Net.RateLimiting.Filters } /// - public bool Passes(RateLimitItemType type, RequestDefinition definition, string host, SecureString? apiKey) + public bool Passes(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey) => definition.Authenticated == _authenticated; } } diff --git a/CryptoExchange.Net/RateLimiting/Filters/ExactPathFilter.cs b/CryptoExchange.Net/RateLimiting/Filters/ExactPathFilter.cs index 2b99964..855a589 100644 --- a/CryptoExchange.Net/RateLimiting/Filters/ExactPathFilter.cs +++ b/CryptoExchange.Net/RateLimiting/Filters/ExactPathFilter.cs @@ -24,7 +24,7 @@ namespace CryptoExchange.Net.RateLimiting.Filters } /// - public bool Passes(RateLimitItemType type, RequestDefinition definition, string host, SecureString? apiKey) + public bool Passes(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey) => string.Equals(definition.Path, _path, StringComparison.OrdinalIgnoreCase); } } diff --git a/CryptoExchange.Net/RateLimiting/Filters/ExactPathsFilter.cs b/CryptoExchange.Net/RateLimiting/Filters/ExactPathsFilter.cs index 82ab123..6d17663 100644 --- a/CryptoExchange.Net/RateLimiting/Filters/ExactPathsFilter.cs +++ b/CryptoExchange.Net/RateLimiting/Filters/ExactPathsFilter.cs @@ -22,7 +22,7 @@ namespace CryptoExchange.Net.RateLimiting.Filters } /// - public bool Passes(RateLimitItemType type, RequestDefinition definition, string host, SecureString? apiKey) + public bool Passes(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey) => _paths.Contains(definition.Path); } } diff --git a/CryptoExchange.Net/RateLimiting/Filters/HostFilter.cs b/CryptoExchange.Net/RateLimiting/Filters/HostFilter.cs index 2101185..3079297 100644 --- a/CryptoExchange.Net/RateLimiting/Filters/HostFilter.cs +++ b/CryptoExchange.Net/RateLimiting/Filters/HostFilter.cs @@ -21,7 +21,7 @@ namespace CryptoExchange.Net.RateLimiting.Filters } /// - public bool Passes(RateLimitItemType type, RequestDefinition definition, string host, SecureString? apiKey) + public bool Passes(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey) => host == _host; } diff --git a/CryptoExchange.Net/RateLimiting/Filters/LimitItemTypeFilter.cs b/CryptoExchange.Net/RateLimiting/Filters/LimitItemTypeFilter.cs index f4277fd..8d9d4bf 100644 --- a/CryptoExchange.Net/RateLimiting/Filters/LimitItemTypeFilter.cs +++ b/CryptoExchange.Net/RateLimiting/Filters/LimitItemTypeFilter.cs @@ -21,7 +21,7 @@ namespace CryptoExchange.Net.RateLimiting.Filters } /// - public bool Passes(RateLimitItemType type, RequestDefinition definition, string host, SecureString? apiKey) + public bool Passes(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey) => type == _type; } } diff --git a/CryptoExchange.Net/RateLimiting/Filters/PathStartFilter.cs b/CryptoExchange.Net/RateLimiting/Filters/PathStartFilter.cs index 402b2de..449ac72 100644 --- a/CryptoExchange.Net/RateLimiting/Filters/PathStartFilter.cs +++ b/CryptoExchange.Net/RateLimiting/Filters/PathStartFilter.cs @@ -22,7 +22,7 @@ namespace CryptoExchange.Net.RateLimiting.Filters } /// - public bool Passes(RateLimitItemType type, RequestDefinition definition, string host, SecureString? apiKey) + public bool Passes(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey) => definition.Path.StartsWith(_path, StringComparison.OrdinalIgnoreCase); } } diff --git a/CryptoExchange.Net/RateLimiting/Guards/RateLimitGuard.cs b/CryptoExchange.Net/RateLimiting/Guards/RateLimitGuard.cs index 6ca5031..0f02b4e 100644 --- a/CryptoExchange.Net/RateLimiting/Guards/RateLimitGuard.cs +++ b/CryptoExchange.Net/RateLimiting/Guards/RateLimitGuard.cs @@ -14,26 +14,26 @@ namespace CryptoExchange.Net.RateLimiting.Guards /// /// Apply guard per host /// - public static Func PerHost { get; } = new Func((def, host, key) => host); + public static Func PerHost { get; } = new Func((def, host, key) => host); /// /// Apply guard per endpoint /// - public static Func PerEndpoint { get; } = new Func((def, host, key) => def.Path + def.Method); + public static Func PerEndpoint { get; } = new Func((def, host, key) => def.Path + def.Method); /// /// Apply guard per API key /// - public static Func PerApiKey { get; } = new Func((def, host, key) => key!.GetString()); + public static Func PerApiKey { get; } = new Func((def, host, key) => key!); /// /// Apply guard per API key per endpoint /// - public static Func PerApiKeyPerEndpoint { get; } = new Func((def, host, key) => key!.GetString() + def.Path + def.Method); + public static Func PerApiKeyPerEndpoint { get; } = new Func((def, host, key) => key! + def.Path + def.Method); private readonly IEnumerable _filters; private readonly Dictionary _trackers; private RateLimitWindowType _windowType; private double? _decayRate; private int? _connectionWeight; - private readonly Func _keySelector; + private readonly Func _keySelector; /// public string Name => "RateLimitGuard"; @@ -60,7 +60,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards /// Type of rate limit window /// The decay per timespan if windowType is DecayWindowTracker /// The weight of a new connection - public RateLimitGuard(Func keySelector, IGuardFilter filter, int limit, TimeSpan timeSpan, RateLimitWindowType windowType, double? decayPerTimeSpan = null, int? connectionWeight = null) + public RateLimitGuard(Func keySelector, IGuardFilter filter, int limit, TimeSpan timeSpan, RateLimitWindowType windowType, double? decayPerTimeSpan = null, int? connectionWeight = null) : this(keySelector, new[] { filter }, limit, timeSpan, windowType, decayPerTimeSpan, connectionWeight) { } @@ -75,7 +75,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards /// Type of rate limit window /// The decay per timespan if windowType is DecayWindowTracker /// The weight of a new connection - public RateLimitGuard(Func keySelector, IEnumerable filters, int limit, TimeSpan timeSpan, RateLimitWindowType windowType, double? decayPerTimeSpan = null, int? connectionWeight = null) + public RateLimitGuard(Func keySelector, IEnumerable filters, int limit, TimeSpan timeSpan, RateLimitWindowType windowType, double? decayPerTimeSpan = null, int? connectionWeight = null) { _filters = filters; _trackers = new Dictionary(); @@ -88,7 +88,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards } /// - public LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string host, SecureString? apiKey, int requestWeight) + public LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight) { foreach(var filter in _filters) { @@ -114,7 +114,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards } /// - public RateLimitState ApplyWeight(RateLimitItemType type, RequestDefinition definition, string host, SecureString? apiKey, int requestWeight) + public RateLimitState ApplyWeight(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight) { foreach (var filter in _filters) { diff --git a/CryptoExchange.Net/RateLimiting/Guards/RetryAfterGuard.cs b/CryptoExchange.Net/RateLimiting/Guards/RetryAfterGuard.cs index 1aa5c04..b09763f 100644 --- a/CryptoExchange.Net/RateLimiting/Guards/RetryAfterGuard.cs +++ b/CryptoExchange.Net/RateLimiting/Guards/RetryAfterGuard.cs @@ -38,7 +38,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards } /// - public LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string host, SecureString? apiKey, int requestWeight) + public LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight) { var dif = (After + _windowBuffer) - DateTime.UtcNow; if (dif <= TimeSpan.Zero) @@ -48,7 +48,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards } /// - public RateLimitState ApplyWeight(RateLimitItemType type, RequestDefinition definition, string host, SecureString? apiKey, int requestWeight) + public RateLimitState ApplyWeight(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight) { return RateLimitState.NotApplied; } diff --git a/CryptoExchange.Net/RateLimiting/Guards/SingleLimitGuard.cs b/CryptoExchange.Net/RateLimiting/Guards/SingleLimitGuard.cs index af11e68..5b174eb 100644 --- a/CryptoExchange.Net/RateLimiting/Guards/SingleLimitGuard.cs +++ b/CryptoExchange.Net/RateLimiting/Guards/SingleLimitGuard.cs @@ -15,19 +15,19 @@ namespace CryptoExchange.Net.RateLimiting.Guards /// /// Default endpoint limit /// - public static Func Default { get; } = new Func((def, host, key) => def.Path + def.Method); + public static Func Default { get; } = new Func((def, host, key) => def.Path + def.Method); /// /// Endpoint limit per API key /// - public static Func PerApiKey { get; } = new Func((def, host, key) => def.Path + def.Method); + public static Func PerApiKey { get; } = new Func((def, host, key) => def.Path + def.Method); private readonly Dictionary _trackers; private readonly RateLimitWindowType _windowType; private readonly double? _decayRate; private readonly int _limit; private readonly TimeSpan _period; - private readonly Func _keySelector; + private readonly Func _keySelector; /// public string Name => "EndpointLimitGuard"; @@ -43,7 +43,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards TimeSpan period, RateLimitWindowType windowType, double? decayRate = null, - Func? keySelector = null) + Func? keySelector = null) { _limit = limit; _period = period; @@ -54,7 +54,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards } /// - public LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string host, SecureString? apiKey, int requestWeight) + public LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight) { var key = _keySelector(definition, host, apiKey); if (!_trackers.TryGetValue(key, out var tracker)) @@ -71,7 +71,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards } /// - public RateLimitState ApplyWeight(RateLimitItemType type, RequestDefinition definition, string host, SecureString? apiKey, int requestWeight) + public RateLimitState ApplyWeight(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight) { var key = _keySelector(definition, host, apiKey); var tracker = _trackers[key]; diff --git a/CryptoExchange.Net/RateLimiting/Interfaces/IGuardFilter.cs b/CryptoExchange.Net/RateLimiting/Interfaces/IGuardFilter.cs index 9381f8b..7599db8 100644 --- a/CryptoExchange.Net/RateLimiting/Interfaces/IGuardFilter.cs +++ b/CryptoExchange.Net/RateLimiting/Interfaces/IGuardFilter.cs @@ -16,6 +16,6 @@ namespace CryptoExchange.Net.RateLimiting.Interfaces /// The host address /// The API key /// True if passed - bool Passes(RateLimitItemType type, RequestDefinition definition, string host, SecureString? apiKey); + bool Passes(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey); } } diff --git a/CryptoExchange.Net/RateLimiting/Interfaces/IRateLimitGate.cs b/CryptoExchange.Net/RateLimiting/Interfaces/IRateLimitGate.cs index 38c1ff8..eb1d754 100644 --- a/CryptoExchange.Net/RateLimiting/Interfaces/IRateLimitGate.cs +++ b/CryptoExchange.Net/RateLimiting/Interfaces/IRateLimitGate.cs @@ -51,7 +51,7 @@ namespace CryptoExchange.Net.RateLimiting.Interfaces /// Behaviour when rate limit is hit /// Cancelation token /// Error if RateLimitingBehaviour is Fail and rate limit is hit - Task ProcessAsync(ILogger logger, int itemId, RateLimitItemType type, RequestDefinition definition, string baseAddress, SecureString? apiKey, int requestWeight, RateLimitingBehaviour behaviour, CancellationToken ct); + Task ProcessAsync(ILogger logger, int itemId, RateLimitItemType type, RequestDefinition definition, string baseAddress, string? apiKey, int requestWeight, RateLimitingBehaviour behaviour, CancellationToken ct); /// /// Enforces the rate limit as defined in the request definition. When a rate limit is hit will wait for the rate limit to pass if RateLimitingBehaviour is Wait, or return an error if it is set to Fail @@ -66,6 +66,6 @@ namespace CryptoExchange.Net.RateLimiting.Interfaces /// Behaviour when rate limit is hit /// Cancelation token /// Error if RateLimitingBehaviour is Fail and rate limit is hit - Task ProcessSingleAsync(ILogger logger, int itemId, IRateLimitGuard guard, RateLimitItemType type, RequestDefinition definition, string baseAddress, SecureString? apiKey, RateLimitingBehaviour behaviour, CancellationToken ct); + Task ProcessSingleAsync(ILogger logger, int itemId, IRateLimitGuard guard, RateLimitItemType type, RequestDefinition definition, string baseAddress, string? apiKey, RateLimitingBehaviour behaviour, CancellationToken ct); } } diff --git a/CryptoExchange.Net/RateLimiting/Interfaces/IRateLimitGuard.cs b/CryptoExchange.Net/RateLimiting/Interfaces/IRateLimitGuard.cs index 7b303c3..7d3749d 100644 --- a/CryptoExchange.Net/RateLimiting/Interfaces/IRateLimitGuard.cs +++ b/CryptoExchange.Net/RateLimiting/Interfaces/IRateLimitGuard.cs @@ -28,7 +28,7 @@ namespace CryptoExchange.Net.RateLimiting.Interfaces /// The API key /// The request weight /// - LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string host, SecureString? apiKey, int requestWeight); + LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight); /// /// Apply the request to this guard with the specified weight @@ -39,6 +39,6 @@ namespace CryptoExchange.Net.RateLimiting.Interfaces /// The API key /// The request weight /// - RateLimitState ApplyWeight(RateLimitItemType type, RequestDefinition definition, string host, SecureString? apiKey, int requestWeight); + RateLimitState ApplyWeight(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight); } } diff --git a/CryptoExchange.Net/RateLimiting/RateLimitGate.cs b/CryptoExchange.Net/RateLimiting/RateLimitGate.cs index afe91dd..cf12c96 100644 --- a/CryptoExchange.Net/RateLimiting/RateLimitGate.cs +++ b/CryptoExchange.Net/RateLimiting/RateLimitGate.cs @@ -36,7 +36,7 @@ namespace CryptoExchange.Net.RateLimiting } /// - public async Task ProcessAsync(ILogger logger, int itemId, RateLimitItemType type, RequestDefinition definition, string host, SecureString? apiKey, int requestWeight, RateLimitingBehaviour rateLimitingBehaviour, CancellationToken ct) + public async Task ProcessAsync(ILogger logger, int itemId, RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, RateLimitingBehaviour rateLimitingBehaviour, CancellationToken ct) { await _semaphore.WaitAsync(ct).ConfigureAwait(false); _waitingCount++; @@ -58,8 +58,8 @@ namespace CryptoExchange.Net.RateLimiting IRateLimitGuard guard, RateLimitItemType type, RequestDefinition definition, - string host, - SecureString? apiKey, + string host, + string? apiKey, RateLimitingBehaviour rateLimitingBehaviour, CancellationToken ct) { @@ -77,7 +77,7 @@ namespace CryptoExchange.Net.RateLimiting } } - private async Task CheckGuardsAsync(IEnumerable guards, ILogger logger, int itemId, RateLimitItemType type, RequestDefinition definition, string host, SecureString? apiKey, int requestWeight, RateLimitingBehaviour rateLimitingBehaviour, CancellationToken ct) + private async Task CheckGuardsAsync(IEnumerable guards, ILogger logger, int itemId, RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, RateLimitingBehaviour rateLimitingBehaviour, CancellationToken ct) { foreach (var guard in guards) { diff --git a/CryptoExchange.Net/Testing/TestHelpers.cs b/CryptoExchange.Net/Testing/TestHelpers.cs index 74df021..ba699d1 100644 --- a/CryptoExchange.Net/Testing/TestHelpers.cs +++ b/CryptoExchange.Net/Testing/TestHelpers.cs @@ -131,9 +131,9 @@ namespace CryptoExchange.Net.Testing client, new Uri(host.AppendPath(path)), method, - uriParams, - bodyParams, - headers, + ref uriParams, + ref bodyParams, + ref headers, true, client.ArraySerialization, client.ParameterPositions[method],