mirror of
https://github.com/JKorf/CryptoExchange.Net
synced 2025-06-08 08:26:20 +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("Four1", 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 output = JsonConvert.DeserializeObject<EnumObject?>($"{{ \"Value\": {val} }}");
|
||||
var output = JsonConvert.DeserializeObject<EnumObject>($"{{ \"Value\": {val} }}");
|
||||
Assert.AreEqual(output.Value, expected);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using CryptoExchange.Net.Authentication;
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
bodyParameters = new SortedDictionary<string, object>();
|
||||
uriParameters = new SortedDictionary<string, object>();
|
||||
headers = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
public override string Sign(string toSign)
|
||||
|
@ -56,10 +56,10 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
|
||||
request.Setup(c => c.GetHeaders()).Returns(() => headers);
|
||||
|
||||
var factory = Mock.Get(RequestFactory);
|
||||
factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<string>(), It.IsAny<int>()))
|
||||
.Callback<HttpMethod, string, int>((method, uri, id) =>
|
||||
factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
|
||||
.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);
|
||||
})
|
||||
.Returns(request.Object);
|
||||
@ -76,7 +76,7 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
|
||||
request.Setup(c => c.GetResponseAsync(It.IsAny<CancellationToken>())).Throws(we);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -99,8 +99,8 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
|
||||
request.Setup(c => c.GetHeaders()).Returns(headers);
|
||||
|
||||
var factory = Mock.Get(RequestFactory);
|
||||
factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<string>(), It.IsAny<int>()))
|
||||
.Callback<HttpMethod, string, int>((method, uri, id) => request.Setup(a => a.Uri).Returns(new Uri(uri)))
|
||||
factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
|
||||
.Callback<HttpMethod, Uri, int>((method, uri, id) => request.Setup(a => a.Uri).Returns(uri))
|
||||
.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)
|
||||
=> new TestAuthProvider(credentials);
|
||||
|
||||
protected override Task<WebCallResult<DateTime>> GetServerTimestampAsync()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override TimeSyncInfo GetTimeSyncInfo()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
=> new TestAuthProvider(credentials);
|
||||
|
||||
protected override Task<WebCallResult<DateTime>> GetServerTimestampAsync()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override TimeSyncInfo GetTimeSyncInfo()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public class TestAuthProvider : AuthenticationProvider
|
||||
@ -142,6 +172,13 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
|
||||
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
|
||||
|
@ -1,6 +1,8 @@
|
||||
using CryptoExchange.Net.Objects;
|
||||
using CryptoExchange.Net.Converters;
|
||||
using CryptoExchange.Net.Objects;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Net.Http;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
@ -35,43 +37,41 @@ namespace CryptoExchange.Net.Authentication
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Authenticate a request where the parameters need to be in the Uri
|
||||
/// Authenticate a request. Output parameters should include the providedParameters input
|
||||
/// </summary>
|
||||
/// <param name="apiClient">The Api client sending the request</param>
|
||||
/// <param name="uri">The uri for the request</param>
|
||||
/// <param name="method">The method of the request</param>
|
||||
/// <param name="parameters">The request parameters</param>
|
||||
/// <param name="headers">The request headers</param>
|
||||
/// <param name="providedParameters">The request parameters</param>
|
||||
/// <param name="auth">If the requests should be authenticated</param>
|
||||
/// <param name="arraySerialization">Array serialization type</param>
|
||||
/// <returns></returns>
|
||||
public abstract void AuthenticateUriRequest(
|
||||
/// <param name="parameterPosition">The position where the providedParameters should go</param>
|
||||
/// <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,
|
||||
Uri uri,
|
||||
HttpMethod method,
|
||||
SortedDictionary<string, object> parameters,
|
||||
Dictionary<string, string> headers,
|
||||
Dictionary<string, object> providedParameters,
|
||||
bool auth,
|
||||
ArrayParametersSerialization arraySerialization);
|
||||
ArrayParametersSerialization arraySerialization,
|
||||
HttpMethodParameterPosition parameterPosition,
|
||||
out SortedDictionary<string, object> uriParameters,
|
||||
out SortedDictionary<string, object> bodyParameters,
|
||||
out Dictionary<string, string> headers
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Authenticate a request where the parameters need to be in the request body
|
||||
/// SHA256 sign the data and return the bytes
|
||||
/// </summary>
|
||||
/// <param name="apiClient">The Api client sending the request</param>
|
||||
/// <param name="uri">The uri for the request</param>
|
||||
/// <param name="method">The method of the request</param>
|
||||
/// <param name="parameters">The request parameters</param>
|
||||
/// <param name="headers">The request headers</param>
|
||||
/// <param name="auth">If the requests should be authenticated</param>
|
||||
/// <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);
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
protected static byte[] SignSHA256Bytes(string data)
|
||||
{
|
||||
using var encryptor = SHA256.Create();
|
||||
return encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SHA256 sign the data and return the hash
|
||||
@ -158,9 +158,18 @@ namespace CryptoExchange.Net.Authentication
|
||||
/// <param name="outputType">String type</param>
|
||||
/// <returns></returns>
|
||||
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);
|
||||
var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
|
||||
var resultBytes = encryptor.ComputeHash(data);
|
||||
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
|
||||
}
|
||||
|
||||
@ -206,5 +215,25 @@ namespace CryptoExchange.Net.Authentication
|
||||
{
|
||||
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,10 +119,13 @@ namespace CryptoExchange.Net
|
||||
{
|
||||
var requestId = NextId();
|
||||
|
||||
var syncTimeResult = await apiClient.SyncTimeAsync().ConfigureAwait(false);
|
||||
if (!syncTimeResult)
|
||||
return syncTimeResult.As<T>(default);
|
||||
|
||||
if (signed)
|
||||
{
|
||||
var syncTimeResult = await apiClient.SyncTimeAsync().ConfigureAwait(false);
|
||||
if (!syncTimeResult)
|
||||
return syncTimeResult.As<T>(default);
|
||||
}
|
||||
|
||||
log.Write(LogLevel.Debug, $"[{requestId}] Creating request for " + uri);
|
||||
if (signed && apiClient.AuthenticationProvider == null)
|
||||
{
|
||||
@ -285,33 +288,40 @@ namespace CryptoExchange.Net
|
||||
int requestId,
|
||||
Dictionary<string, string>? additionalHeaders)
|
||||
{
|
||||
SortedDictionary<string, object> sortedParameters = new SortedDictionary<string, object>(GetParameterComparer());
|
||||
if (parameters != null)
|
||||
sortedParameters = new SortedDictionary<string, object>(parameters, GetParameterComparer());
|
||||
|
||||
parameters ??= new Dictionary<string, object>();
|
||||
if (parameterPosition == HttpMethodParameterPosition.InUri)
|
||||
{
|
||||
foreach (var parameter in sortedParameters)
|
||||
foreach (var parameter in parameters)
|
||||
uri = uri.AddQueryParmeter(parameter.Key, parameter.Value.ToString());
|
||||
}
|
||||
|
||||
var length = sortedParameters.Count;
|
||||
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)
|
||||
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)
|
||||
apiClient.AuthenticationProvider.AuthenticateUriRequest(apiClient, uri, method, sortedParameters, headers, signed, arraySerialization);
|
||||
else
|
||||
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
|
||||
if (sortedParameters.Count != length)
|
||||
uri = uri.SetParameters(sortedParameters);
|
||||
if (!uriParameters.ContainsKey(param.Key) && !bodyParameters.ContainsKey(param.Key))
|
||||
throw new Exception($"Missing parameter {param.Key} after authentication processing. AuthenticationProvider implementation " +
|
||||
$"should return provided parameters in either the uri or body parameters output");
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
var request = RequestFactory.Create(method, uri, requestId);
|
||||
request.Accept = Constants.JsonContentHeader;
|
||||
|
||||
@ -335,8 +345,8 @@ namespace CryptoExchange.Net
|
||||
if (parameterPosition == HttpMethodParameterPosition.InBody)
|
||||
{
|
||||
var contentType = requestBodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader;
|
||||
if (sortedParameters?.Any() == true)
|
||||
WriteParamBody(request, sortedParameters, contentType);
|
||||
if (bodyParameters.Any())
|
||||
WriteParamBody(request, bodyParameters, contentType);
|
||||
else
|
||||
request.SetContent(requestBodyEmptyContent, contentType);
|
||||
}
|
||||
@ -386,8 +396,6 @@ namespace CryptoExchange.Net
|
||||
//return request;
|
||||
}
|
||||
|
||||
protected virtual IComparer<string> GetParameterComparer() => null;
|
||||
|
||||
/// <summary>
|
||||
/// Writes the parameters of the request to the request object body
|
||||
/// </summary>
|
||||
|
@ -14,8 +14,16 @@ namespace CryptoExchange.Net
|
||||
/// </summary>
|
||||
public abstract class RestApiClient: BaseApiClient
|
||||
{
|
||||
protected abstract TimeSyncModel GetTimeSyncParameters();
|
||||
protected abstract void UpdateTimeOffset(TimeSpan offset);
|
||||
/// <summary>
|
||||
/// 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();
|
||||
|
||||
/// <summary>
|
||||
@ -33,8 +41,6 @@ namespace CryptoExchange.Net
|
||||
/// </summary>
|
||||
internal IEnumerable<IRateLimiter> RateLimiters { get; }
|
||||
|
||||
private Log _log;
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
@ -58,12 +64,12 @@ namespace CryptoExchange.Net
|
||||
|
||||
internal async Task<WebCallResult<bool>> SyncTimeAsync()
|
||||
{
|
||||
var timeSyncParams = GetTimeSyncParameters();
|
||||
if (await timeSyncParams.Semaphore.WaitAsync(0).ConfigureAwait(false))
|
||||
var timeSyncParams = GetTimeSyncInfo();
|
||||
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);
|
||||
}
|
||||
|
||||
@ -71,7 +77,7 @@ namespace CryptoExchange.Net
|
||||
var result = await GetServerTimestampAsync().ConfigureAwait(false);
|
||||
if (!result)
|
||||
{
|
||||
timeSyncParams.Semaphore.Release();
|
||||
timeSyncParams.TimeSyncState.Semaphore.Release();
|
||||
return result.As(false);
|
||||
}
|
||||
|
||||
@ -82,7 +88,7 @@ namespace CryptoExchange.Net
|
||||
result = await GetServerTimestampAsync().ConfigureAwait(false);
|
||||
if (!result)
|
||||
{
|
||||
timeSyncParams.Semaphore.Release();
|
||||
timeSyncParams.TimeSyncState.Semaphore.Release();
|
||||
return result.As(false);
|
||||
}
|
||||
}
|
||||
@ -92,13 +98,13 @@ namespace CryptoExchange.Net
|
||||
if (offset.TotalMilliseconds >= 0 && offset.TotalMilliseconds < 500)
|
||||
{
|
||||
// Small offset, probably mainly due to ping. Don't adjust time
|
||||
UpdateTimeOffset(offset);
|
||||
timeSyncParams.Semaphore.Release();
|
||||
timeSyncParams.UpdateTimeOffset(offset);
|
||||
timeSyncParams.TimeSyncState.Semaphore.Release();
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateTimeOffset(offset);
|
||||
timeSyncParams.Semaphore.Release();
|
||||
timeSyncParams.UpdateTimeOffset(offset);
|
||||
timeSyncParams.TimeSyncState.Semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
@ -41,7 +41,7 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="5.0.3">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="6.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
@ -151,7 +151,7 @@ namespace CryptoExchange.Net
|
||||
public static string ToFormData(this SortedDictionary<string, object> parameters)
|
||||
{
|
||||
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)
|
||||
{
|
||||
@ -433,6 +433,25 @@ namespace CryptoExchange.Net
|
||||
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>
|
||||
/// 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