mirror of
https://github.com/JKorf/CryptoExchange.Net
synced 2025-06-08 16:36:15 +00:00
Refactoring and comments
This commit is contained in:
parent
b7cd6a866a
commit
5c665ad54c
@ -140,10 +140,10 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
[TestCase("four", TestEnum.Four)]
|
[TestCase("four", TestEnum.Four)]
|
||||||
[TestCase("Four1", null)]
|
[TestCase("Four1", null)]
|
||||||
[TestCase(null, null)]
|
[TestCase(null, null)]
|
||||||
public void TestEnumConverterNullableDeserializeTests(string? value, TestEnum? expected)
|
public void TestEnumConverterNullableDeserializeTests(string value, TestEnum? expected)
|
||||||
{
|
{
|
||||||
var val = value == null ? "null" : $"\"{value}\"";
|
var val = value == null ? "null" : $"\"{value}\"";
|
||||||
var output = JsonConvert.DeserializeObject<EnumObject?>($"{{ \"Value\": {val} }}");
|
var output = JsonConvert.DeserializeObject<EnumObject>($"{{ \"Value\": {val} }}");
|
||||||
Assert.AreEqual(output.Value, expected);
|
Assert.AreEqual(output.Value, expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using CryptoExchange.Net.Authentication;
|
using CryptoExchange.Net.Authentication;
|
||||||
using CryptoExchange.Net.Objects;
|
using CryptoExchange.Net.Objects;
|
||||||
@ -33,14 +34,11 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Dictionary<string, string> AddAuthenticationToHeaders(string uri, HttpMethod method, Dictionary<string, object> parameters, bool signed, HttpMethodParameterPosition postParameters, ArrayParametersSerialization arraySerialization)
|
public override void AuthenticateRequest(RestApiClient apiClient, Uri uri, HttpMethod method, Dictionary<string, object> providedParameters, bool auth, ArrayParametersSerialization arraySerialization, HttpMethodParameterPosition parameterPosition, out SortedDictionary<string, object> uriParameters, out SortedDictionary<string, object> bodyParameters, out Dictionary<string, string> headers)
|
||||||
{
|
{
|
||||||
return base.AddAuthenticationToHeaders(uri, method, parameters, signed, postParameters, arraySerialization);
|
bodyParameters = new SortedDictionary<string, object>();
|
||||||
}
|
uriParameters = new SortedDictionary<string, object>();
|
||||||
|
headers = new Dictionary<string, string>();
|
||||||
public override Dictionary<string, object> AddAuthenticationToParameters(string uri, HttpMethod method, Dictionary<string, object> parameters, bool signed, HttpMethodParameterPosition postParameters, ArrayParametersSerialization arraySerialization)
|
|
||||||
{
|
|
||||||
return base.AddAuthenticationToParameters(uri, method, parameters, signed, postParameters, arraySerialization);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string Sign(string toSign)
|
public override string Sign(string toSign)
|
||||||
|
@ -56,10 +56,10 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
|
|||||||
request.Setup(c => c.GetHeaders()).Returns(() => headers);
|
request.Setup(c => c.GetHeaders()).Returns(() => headers);
|
||||||
|
|
||||||
var factory = Mock.Get(RequestFactory);
|
var factory = Mock.Get(RequestFactory);
|
||||||
factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<string>(), It.IsAny<int>()))
|
factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
|
||||||
.Callback<HttpMethod, string, int>((method, uri, id) =>
|
.Callback<HttpMethod, Uri, int>((method, uri, id) =>
|
||||||
{
|
{
|
||||||
request.Setup(a => a.Uri).Returns(new Uri(uri));
|
request.Setup(a => a.Uri).Returns(uri);
|
||||||
request.Setup(a => a.Method).Returns(method);
|
request.Setup(a => a.Method).Returns(method);
|
||||||
})
|
})
|
||||||
.Returns(request.Object);
|
.Returns(request.Object);
|
||||||
@ -76,7 +76,7 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
|
|||||||
request.Setup(c => c.GetResponseAsync(It.IsAny<CancellationToken>())).Throws(we);
|
request.Setup(c => c.GetResponseAsync(It.IsAny<CancellationToken>())).Throws(we);
|
||||||
|
|
||||||
var factory = Mock.Get(RequestFactory);
|
var factory = Mock.Get(RequestFactory);
|
||||||
factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<string>(), It.IsAny<int>()))
|
factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
|
||||||
.Returns(request.Object);
|
.Returns(request.Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,8 +99,8 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
|
|||||||
request.Setup(c => c.GetHeaders()).Returns(headers);
|
request.Setup(c => c.GetHeaders()).Returns(headers);
|
||||||
|
|
||||||
var factory = Mock.Get(RequestFactory);
|
var factory = Mock.Get(RequestFactory);
|
||||||
factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<string>(), It.IsAny<int>()))
|
factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
|
||||||
.Callback<HttpMethod, string, int>((method, uri, id) => request.Setup(a => a.Uri).Returns(new Uri(uri)))
|
.Callback<HttpMethod, Uri, int>((method, uri, id) => request.Setup(a => a.Uri).Returns(uri))
|
||||||
.Returns(request.Object);
|
.Returns(request.Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,8 +122,23 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override TimeSpan GetTimeOffset()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
protected override AuthenticationProvider CreateAuthenticationProvider(ApiCredentials credentials)
|
protected override AuthenticationProvider CreateAuthenticationProvider(ApiCredentials credentials)
|
||||||
=> new TestAuthProvider(credentials);
|
=> new TestAuthProvider(credentials);
|
||||||
|
|
||||||
|
protected override Task<WebCallResult<DateTime>> GetServerTimestampAsync()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override TimeSyncInfo GetTimeSyncInfo()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TestRestApi2Client : RestApiClient
|
public class TestRestApi2Client : RestApiClient
|
||||||
@ -133,8 +148,23 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override TimeSpan GetTimeOffset()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
protected override AuthenticationProvider CreateAuthenticationProvider(ApiCredentials credentials)
|
protected override AuthenticationProvider CreateAuthenticationProvider(ApiCredentials credentials)
|
||||||
=> new TestAuthProvider(credentials);
|
=> new TestAuthProvider(credentials);
|
||||||
|
|
||||||
|
protected override Task<WebCallResult<DateTime>> GetServerTimestampAsync()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override TimeSyncInfo GetTimeSyncInfo()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TestAuthProvider : AuthenticationProvider
|
public class TestAuthProvider : AuthenticationProvider
|
||||||
@ -142,6 +172,13 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
|
|||||||
public TestAuthProvider(ApiCredentials credentials) : base(credentials)
|
public TestAuthProvider(ApiCredentials credentials) : base(credentials)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void AuthenticateRequest(RestApiClient apiClient, Uri uri, HttpMethod method, Dictionary<string, object> providedParameters, bool auth, ArrayParametersSerialization arraySerialization, HttpMethodParameterPosition parameterPosition, out SortedDictionary<string, object> uriParameters, out SortedDictionary<string, object> bodyParameters, out Dictionary<string, string> headers)
|
||||||
|
{
|
||||||
|
uriParameters = parameterPosition == HttpMethodParameterPosition.InUri ? new SortedDictionary<string, object>(providedParameters) : new SortedDictionary<string, object>();
|
||||||
|
bodyParameters = parameterPosition == HttpMethodParameterPosition.InBody ? new SortedDictionary<string, object>(providedParameters) : new SortedDictionary<string, object>();
|
||||||
|
headers = new Dictionary<string, string>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ParseErrorTestRestClient: TestRestClient
|
public class ParseErrorTestRestClient: TestRestClient
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
using CryptoExchange.Net.Objects;
|
using CryptoExchange.Net.Converters;
|
||||||
|
using CryptoExchange.Net.Objects;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@ -35,43 +37,41 @@ namespace CryptoExchange.Net.Authentication
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Authenticate a request where the parameters need to be in the Uri
|
/// Authenticate a request. Output parameters should include the providedParameters input
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="apiClient">The Api client sending the request</param>
|
/// <param name="apiClient">The Api client sending the request</param>
|
||||||
/// <param name="uri">The uri for the request</param>
|
/// <param name="uri">The uri for the request</param>
|
||||||
/// <param name="method">The method of the request</param>
|
/// <param name="method">The method of the request</param>
|
||||||
/// <param name="parameters">The request parameters</param>
|
/// <param name="providedParameters">The request parameters</param>
|
||||||
/// <param name="headers">The request headers</param>
|
|
||||||
/// <param name="auth">If the requests should be authenticated</param>
|
/// <param name="auth">If the requests should be authenticated</param>
|
||||||
/// <param name="arraySerialization">Array serialization type</param>
|
/// <param name="arraySerialization">Array serialization type</param>
|
||||||
/// <returns></returns>
|
/// <param name="parameterPosition">The position where the providedParameters should go</param>
|
||||||
public abstract void AuthenticateUriRequest(
|
/// <param name="uriParameters">Parameters that need to be in the Uri of the request. Should include the provided parameters if they should go in the uri</param>
|
||||||
|
/// <param name="bodyParameters">Parameters that need to be in the body of the request. Should include the provided parameters if they should go in the body</param>
|
||||||
|
/// <param name="headers">The headers that should be send with the request</param>
|
||||||
|
public abstract void AuthenticateRequest(
|
||||||
RestApiClient apiClient,
|
RestApiClient apiClient,
|
||||||
Uri uri,
|
Uri uri,
|
||||||
HttpMethod method,
|
HttpMethod method,
|
||||||
SortedDictionary<string, object> parameters,
|
Dictionary<string, object> providedParameters,
|
||||||
Dictionary<string, string> headers,
|
|
||||||
bool auth,
|
bool auth,
|
||||||
ArrayParametersSerialization arraySerialization);
|
ArrayParametersSerialization arraySerialization,
|
||||||
|
HttpMethodParameterPosition parameterPosition,
|
||||||
|
out SortedDictionary<string, object> uriParameters,
|
||||||
|
out SortedDictionary<string, object> bodyParameters,
|
||||||
|
out Dictionary<string, string> headers
|
||||||
|
);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Authenticate a request where the parameters need to be in the request body
|
/// SHA256 sign the data and return the bytes
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="apiClient">The Api client sending the request</param>
|
/// <param name="data"></param>
|
||||||
/// <param name="uri">The uri for the request</param>
|
/// <returns></returns>
|
||||||
/// <param name="method">The method of the request</param>
|
protected static byte[] SignSHA256Bytes(string data)
|
||||||
/// <param name="parameters">The request parameters</param>
|
{
|
||||||
/// <param name="headers">The request headers</param>
|
using var encryptor = SHA256.Create();
|
||||||
/// <param name="auth">If the requests should be authenticated</param>
|
return encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
|
||||||
/// <param name="arraySerialization">Array serialization type</param>
|
}
|
||||||
public abstract void AuthenticateBodyRequest(
|
|
||||||
RestApiClient apiClient,
|
|
||||||
Uri uri,
|
|
||||||
HttpMethod method,
|
|
||||||
SortedDictionary<string, object> parameters,
|
|
||||||
Dictionary<string, string> headers,
|
|
||||||
bool auth,
|
|
||||||
ArrayParametersSerialization arraySerialization);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SHA256 sign the data and return the hash
|
/// SHA256 sign the data and return the hash
|
||||||
@ -158,9 +158,18 @@ namespace CryptoExchange.Net.Authentication
|
|||||||
/// <param name="outputType">String type</param>
|
/// <param name="outputType">String type</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected string SignHMACSHA512(string data, SignOutputType? outputType = null)
|
protected string SignHMACSHA512(string data, SignOutputType? outputType = null)
|
||||||
|
=> SignHMACSHA512(Encoding.UTF8.GetBytes(data), outputType);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// HMACSHA512 sign the data and return the hash
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">Data to sign</param>
|
||||||
|
/// <param name="outputType">String type</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected string SignHMACSHA512(byte[] data, SignOutputType? outputType = null)
|
||||||
{
|
{
|
||||||
using var encryptor = new HMACSHA512(_sBytes);
|
using var encryptor = new HMACSHA512(_sBytes);
|
||||||
var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
|
var resultBytes = encryptor.ComputeHash(data);
|
||||||
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
|
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,5 +215,25 @@ namespace CryptoExchange.Net.Authentication
|
|||||||
{
|
{
|
||||||
return Convert.ToBase64String(buff);
|
return Convert.ToBase64String(buff);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get current timestamp including the time sync offset from the api client
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="apiClient"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected static DateTime GetTimestamp(RestApiClient apiClient)
|
||||||
|
{
|
||||||
|
return DateTime.UtcNow.Add(apiClient?.GetTimeOffset() ?? TimeSpan.Zero)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get millisecond timestamp as a string including the time sync offset from the api client
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="apiClient"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected static string GetMillisecondTimestamp(RestApiClient apiClient)
|
||||||
|
{
|
||||||
|
return DateTimeConverter.ConvertToMilliseconds(GetTimestamp(apiClient)).Value.ToString(CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -119,9 +119,12 @@ namespace CryptoExchange.Net
|
|||||||
{
|
{
|
||||||
var requestId = NextId();
|
var requestId = NextId();
|
||||||
|
|
||||||
var syncTimeResult = await apiClient.SyncTimeAsync().ConfigureAwait(false);
|
if (signed)
|
||||||
if (!syncTimeResult)
|
{
|
||||||
return syncTimeResult.As<T>(default);
|
var syncTimeResult = await apiClient.SyncTimeAsync().ConfigureAwait(false);
|
||||||
|
if (!syncTimeResult)
|
||||||
|
return syncTimeResult.As<T>(default);
|
||||||
|
}
|
||||||
|
|
||||||
log.Write(LogLevel.Debug, $"[{requestId}] Creating request for " + uri);
|
log.Write(LogLevel.Debug, $"[{requestId}] Creating request for " + uri);
|
||||||
if (signed && apiClient.AuthenticationProvider == null)
|
if (signed && apiClient.AuthenticationProvider == null)
|
||||||
@ -285,32 +288,39 @@ namespace CryptoExchange.Net
|
|||||||
int requestId,
|
int requestId,
|
||||||
Dictionary<string, string>? additionalHeaders)
|
Dictionary<string, string>? additionalHeaders)
|
||||||
{
|
{
|
||||||
SortedDictionary<string, object> sortedParameters = new SortedDictionary<string, object>(GetParameterComparer());
|
parameters ??= new Dictionary<string, object>();
|
||||||
if (parameters != null)
|
|
||||||
sortedParameters = new SortedDictionary<string, object>(parameters, GetParameterComparer());
|
|
||||||
|
|
||||||
if (parameterPosition == HttpMethodParameterPosition.InUri)
|
if (parameterPosition == HttpMethodParameterPosition.InUri)
|
||||||
{
|
{
|
||||||
foreach (var parameter in sortedParameters)
|
foreach (var parameter in parameters)
|
||||||
uri = uri.AddQueryParmeter(parameter.Key, parameter.Value.ToString());
|
uri = uri.AddQueryParmeter(parameter.Key, parameter.Value.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
var length = sortedParameters.Count;
|
|
||||||
var headers = new Dictionary<string, string>();
|
var headers = new Dictionary<string, string>();
|
||||||
|
var uriParameters = parameterPosition == HttpMethodParameterPosition.InUri ? new SortedDictionary<string, object>(parameters) : new SortedDictionary<string, object>();
|
||||||
|
var bodyParameters = parameterPosition == HttpMethodParameterPosition.InBody ? new SortedDictionary<string, object>(parameters) : new SortedDictionary<string, object>();
|
||||||
if (apiClient.AuthenticationProvider != null)
|
if (apiClient.AuthenticationProvider != null)
|
||||||
|
apiClient.AuthenticationProvider.AuthenticateRequest(
|
||||||
|
apiClient,
|
||||||
|
uri,
|
||||||
|
method,
|
||||||
|
parameters,
|
||||||
|
signed,
|
||||||
|
arraySerialization,
|
||||||
|
parameterPosition,
|
||||||
|
out uriParameters,
|
||||||
|
out bodyParameters,
|
||||||
|
out headers);
|
||||||
|
|
||||||
|
// Sanity check
|
||||||
|
foreach(var param in parameters)
|
||||||
{
|
{
|
||||||
if(parameterPosition == HttpMethodParameterPosition.InUri)
|
if (!uriParameters.ContainsKey(param.Key) && !bodyParameters.ContainsKey(param.Key))
|
||||||
apiClient.AuthenticationProvider.AuthenticateUriRequest(apiClient, uri, method, sortedParameters, headers, signed, arraySerialization);
|
throw new Exception($"Missing parameter {param.Key} after authentication processing. AuthenticationProvider implementation " +
|
||||||
else
|
$"should return provided parameters in either the uri or body parameters output");
|
||||||
apiClient.AuthenticationProvider.AuthenticateBodyRequest(apiClient, uri, method, sortedParameters, headers, signed, arraySerialization);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parameterPosition == HttpMethodParameterPosition.InUri)
|
// 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(uriParameters);
|
||||||
// Add the auth parameters to the uri, start with a new URI to be able to sort the parameters including the auth parameters
|
|
||||||
if (sortedParameters.Count != length)
|
|
||||||
uri = uri.SetParameters(sortedParameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
var request = RequestFactory.Create(method, uri, requestId);
|
var request = RequestFactory.Create(method, uri, requestId);
|
||||||
request.Accept = Constants.JsonContentHeader;
|
request.Accept = Constants.JsonContentHeader;
|
||||||
@ -335,8 +345,8 @@ namespace CryptoExchange.Net
|
|||||||
if (parameterPosition == HttpMethodParameterPosition.InBody)
|
if (parameterPosition == HttpMethodParameterPosition.InBody)
|
||||||
{
|
{
|
||||||
var contentType = requestBodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader;
|
var contentType = requestBodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader;
|
||||||
if (sortedParameters?.Any() == true)
|
if (bodyParameters.Any())
|
||||||
WriteParamBody(request, sortedParameters, contentType);
|
WriteParamBody(request, bodyParameters, contentType);
|
||||||
else
|
else
|
||||||
request.SetContent(requestBodyEmptyContent, contentType);
|
request.SetContent(requestBodyEmptyContent, contentType);
|
||||||
}
|
}
|
||||||
@ -386,8 +396,6 @@ namespace CryptoExchange.Net
|
|||||||
//return request;
|
//return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual IComparer<string> GetParameterComparer() => null;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Writes the parameters of the request to the request object body
|
/// Writes the parameters of the request to the request object body
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -14,8 +14,16 @@ namespace CryptoExchange.Net
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class RestApiClient: BaseApiClient
|
public abstract class RestApiClient: BaseApiClient
|
||||||
{
|
{
|
||||||
protected abstract TimeSyncModel GetTimeSyncParameters();
|
/// <summary>
|
||||||
protected abstract void UpdateTimeOffset(TimeSpan offset);
|
/// Get time sync info for an API client
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected abstract TimeSyncInfo GetTimeSyncInfo();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get time offset for an API client
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
public abstract TimeSpan GetTimeOffset();
|
public abstract TimeSpan GetTimeOffset();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -33,8 +41,6 @@ namespace CryptoExchange.Net
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal IEnumerable<IRateLimiter> RateLimiters { get; }
|
internal IEnumerable<IRateLimiter> RateLimiters { get; }
|
||||||
|
|
||||||
private Log _log;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ctor
|
/// ctor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -58,12 +64,12 @@ namespace CryptoExchange.Net
|
|||||||
|
|
||||||
internal async Task<WebCallResult<bool>> SyncTimeAsync()
|
internal async Task<WebCallResult<bool>> SyncTimeAsync()
|
||||||
{
|
{
|
||||||
var timeSyncParams = GetTimeSyncParameters();
|
var timeSyncParams = GetTimeSyncInfo();
|
||||||
if (await timeSyncParams.Semaphore.WaitAsync(0).ConfigureAwait(false))
|
if (await timeSyncParams.TimeSyncState.Semaphore.WaitAsync(0).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
if (!timeSyncParams.SyncTime || (DateTime.UtcNow - timeSyncParams.LastSyncTime < TimeSpan.FromHours(1)))
|
if (!timeSyncParams.SyncTime || (DateTime.UtcNow - timeSyncParams.TimeSyncState.LastSyncTime < TimeSpan.FromHours(1)))
|
||||||
{
|
{
|
||||||
timeSyncParams.Semaphore.Release();
|
timeSyncParams.TimeSyncState.Semaphore.Release();
|
||||||
return new WebCallResult<bool>(null, null, true, null);
|
return new WebCallResult<bool>(null, null, true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,7 +77,7 @@ namespace CryptoExchange.Net
|
|||||||
var result = await GetServerTimestampAsync().ConfigureAwait(false);
|
var result = await GetServerTimestampAsync().ConfigureAwait(false);
|
||||||
if (!result)
|
if (!result)
|
||||||
{
|
{
|
||||||
timeSyncParams.Semaphore.Release();
|
timeSyncParams.TimeSyncState.Semaphore.Release();
|
||||||
return result.As(false);
|
return result.As(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,7 +88,7 @@ namespace CryptoExchange.Net
|
|||||||
result = await GetServerTimestampAsync().ConfigureAwait(false);
|
result = await GetServerTimestampAsync().ConfigureAwait(false);
|
||||||
if (!result)
|
if (!result)
|
||||||
{
|
{
|
||||||
timeSyncParams.Semaphore.Release();
|
timeSyncParams.TimeSyncState.Semaphore.Release();
|
||||||
return result.As(false);
|
return result.As(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -92,13 +98,13 @@ namespace CryptoExchange.Net
|
|||||||
if (offset.TotalMilliseconds >= 0 && offset.TotalMilliseconds < 500)
|
if (offset.TotalMilliseconds >= 0 && offset.TotalMilliseconds < 500)
|
||||||
{
|
{
|
||||||
// Small offset, probably mainly due to ping. Don't adjust time
|
// Small offset, probably mainly due to ping. Don't adjust time
|
||||||
UpdateTimeOffset(offset);
|
timeSyncParams.UpdateTimeOffset(offset);
|
||||||
timeSyncParams.Semaphore.Release();
|
timeSyncParams.TimeSyncState.Semaphore.Release();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
UpdateTimeOffset(offset);
|
timeSyncParams.UpdateTimeOffset(offset);
|
||||||
timeSyncParams.Semaphore.Release();
|
timeSyncParams.TimeSyncState.Semaphore.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
|
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@ -41,7 +41,7 @@
|
|||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="5.0.3">
|
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="6.0.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
@ -151,7 +151,7 @@ namespace CryptoExchange.Net
|
|||||||
public static string ToFormData(this SortedDictionary<string, object> parameters)
|
public static string ToFormData(this SortedDictionary<string, object> parameters)
|
||||||
{
|
{
|
||||||
var formData = HttpUtility.ParseQueryString(string.Empty);
|
var formData = HttpUtility.ParseQueryString(string.Empty);
|
||||||
foreach (var kvp in parameters.OrderBy(p => p.Key))
|
foreach (var kvp in parameters)
|
||||||
{
|
{
|
||||||
if (kvp.Value.GetType().IsArray)
|
if (kvp.Value.GetType().IsArray)
|
||||||
{
|
{
|
||||||
@ -433,6 +433,25 @@ namespace CryptoExchange.Net
|
|||||||
return uriBuilder.Uri;
|
return uriBuilder.Uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new uri with the provided parameters as query
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="parameters"></param>
|
||||||
|
/// <param name="baseUri"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static Uri SetParameters(this Uri baseUri, IOrderedEnumerable<KeyValuePair<string, object>> parameters)
|
||||||
|
{
|
||||||
|
var uriBuilder = new UriBuilder();
|
||||||
|
uriBuilder.Scheme = baseUri.Scheme;
|
||||||
|
uriBuilder.Host = baseUri.Host;
|
||||||
|
uriBuilder.Path = baseUri.AbsolutePath;
|
||||||
|
var httpValueCollection = HttpUtility.ParseQueryString(string.Empty);
|
||||||
|
foreach (var parameter in parameters)
|
||||||
|
httpValueCollection.Add(parameter.Key, parameter.Value.ToString());
|
||||||
|
uriBuilder.Query = httpValueCollection.ToString();
|
||||||
|
return uriBuilder.Uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add parameter to URI
|
/// Add parameter to URI
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace CryptoExchange.Net.Objects
|
|
||||||
{
|
|
||||||
public class TimeSyncModel
|
|
||||||
{
|
|
||||||
public bool SyncTime { get; set; }
|
|
||||||
public SemaphoreSlim Semaphore { get; set; }
|
|
||||||
public DateTime LastSyncTime { get; set; }
|
|
||||||
|
|
||||||
public TimeSyncModel(bool syncTime, SemaphoreSlim semaphore, DateTime lastSyncTime)
|
|
||||||
{
|
|
||||||
SyncTime = syncTime;
|
|
||||||
Semaphore = semaphore;
|
|
||||||
LastSyncTime = lastSyncTime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
80
CryptoExchange.Net/Objects/TimeSyncState.cs
Normal file
80
CryptoExchange.Net/Objects/TimeSyncState.cs
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using CryptoExchange.Net.Logging;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace CryptoExchange.Net.Objects
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The time synchronization state of an API client
|
||||||
|
/// </summary>
|
||||||
|
public class TimeSyncState
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Semaphore to use for checking the time syncing. Should be shared instance among the API client
|
||||||
|
/// </summary>
|
||||||
|
public SemaphoreSlim Semaphore { get; }
|
||||||
|
/// <summary>
|
||||||
|
/// Last sync time for the API client
|
||||||
|
/// </summary>
|
||||||
|
public DateTime LastSyncTime { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Time offset for the API client
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan TimeOffset { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ctor
|
||||||
|
/// </summary>
|
||||||
|
public TimeSyncState()
|
||||||
|
{
|
||||||
|
Semaphore = new SemaphoreSlim(1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Time synchronization info
|
||||||
|
/// </summary>
|
||||||
|
public class TimeSyncInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Logger
|
||||||
|
/// </summary>
|
||||||
|
public Log Log { get; }
|
||||||
|
/// <summary>
|
||||||
|
/// Should synchronize time
|
||||||
|
/// </summary>
|
||||||
|
public bool SyncTime { get; }
|
||||||
|
/// <summary>
|
||||||
|
/// Time sync state for the API client
|
||||||
|
/// </summary>
|
||||||
|
public TimeSyncState TimeSyncState { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ctor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="log"></param>
|
||||||
|
/// <param name="syncTime"></param>
|
||||||
|
/// <param name="syncState"></param>
|
||||||
|
public TimeSyncInfo(Log log, bool syncTime, TimeSyncState syncState)
|
||||||
|
{
|
||||||
|
Log = log;
|
||||||
|
SyncTime = syncTime;
|
||||||
|
TimeSyncState = syncState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the time offset
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset"></param>
|
||||||
|
public void UpdateTimeOffset(TimeSpan offset)
|
||||||
|
{
|
||||||
|
TimeSyncState.LastSyncTime = DateTime.UtcNow;
|
||||||
|
if (offset.TotalMilliseconds > 0 && offset.TotalMilliseconds < 500)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Log.Write(LogLevel.Information, $"Time offset set to {Math.Round(offset.TotalMilliseconds)}ms");
|
||||||
|
TimeSyncState.TimeOffset = offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user