1
0
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:
Jkorf 2021-12-10 16:35:42 +01:00
parent b7cd6a866a
commit 5c665ad54c
10 changed files with 261 additions and 105 deletions

View File

@ -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);
} }
} }

View File

@ -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)

View File

@ -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

View File

@ -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);
}
} }
} }

View File

@ -119,9 +119,12 @@ namespace CryptoExchange.Net
{ {
var requestId = NextId(); var requestId = NextId();
if (signed)
{
var syncTimeResult = await apiClient.SyncTimeAsync().ConfigureAwait(false); var syncTimeResult = await apiClient.SyncTimeAsync().ConfigureAwait(false);
if (!syncTimeResult) if (!syncTimeResult)
return syncTimeResult.As<T>(default); 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 // 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(uriParameters);
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>

View File

@ -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();
} }
} }

View File

@ -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>

View File

@ -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

View File

@ -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;
}
}
}

View 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;
}
}
}