1
0
mirror of https://github.com/JKorf/CryptoExchange.Net synced 2025-07-27 03:36:52 +00:00

Wip, support for time syncing, refactoring authentication

This commit is contained in:
Jkorf 2021-12-08 16:20:44 +01:00
parent 8b479547ab
commit c2105fe690
9 changed files with 402 additions and 64 deletions

View File

@ -1,6 +1,9 @@
using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
namespace CryptoExchange.Net.Authentication namespace CryptoExchange.Net.Authentication
{ {
@ -14,45 +17,151 @@ namespace CryptoExchange.Net.Authentication
/// </summary> /// </summary>
public ApiCredentials Credentials { get; } public ApiCredentials Credentials { get; }
/// <summary>
/// </summary>
protected byte[] _sBytes;
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
/// <param name="credentials"></param> /// <param name="credentials"></param>
protected AuthenticationProvider(ApiCredentials credentials) protected AuthenticationProvider(ApiCredentials credentials)
{ {
if (credentials.Secret == null)
throw new ArgumentException("ApiKey/Secret needed");
Credentials = credentials; Credentials = credentials;
_sBytes = Encoding.UTF8.GetBytes(credentials.Secret.GetString());
} }
/// <summary> /// <summary>
/// Add authentication to the parameter list based on the provided credentials /// Authenticate a request where the parameters need to be in the Uri
/// </summary> /// </summary>
/// <param name="uri">The uri the request is for</param> /// <param name="apiClient">The Api client sending the request</param>
/// <param name="method">The HTTP method of the request</param> /// <param name="uri">The uri for the request</param>
/// <param name="parameters">The provided parameters for the request</param> /// <param name="method">The method of the request</param>
/// <param name="signed">Wether or not the request needs to be signed. If not typically the parameters list can just be returned</param> /// <param name="parameters">The request parameters</param>
/// <param name="parameterPosition">Where parameters are placed, in the URI or in the request body</param> /// <param name="headers">The request headers</param>
/// <param name="arraySerialization">How array parameters are serialized</param> /// <param name="auth">If the requests should be authenticated</param>
/// <returns>Should return the original parameter list including any authentication parameters needed</returns> /// <param name="arraySerialization">Array serialization type</param>
public virtual Dictionary<string, object> AddAuthenticationToParameters(string uri, HttpMethod method, Dictionary<string, object> parameters, bool signed, /// <returns></returns>
HttpMethodParameterPosition parameterPosition, ArrayParametersSerialization arraySerialization) public abstract void AuthenticateUriRequest(
RestApiClient apiClient,
ref Uri uri,
HttpMethod method,
SortedDictionary<string, object> parameters,
Dictionary<string, string> headers,
bool auth,
ArrayParametersSerialization arraySerialization);
/// <summary>
/// Authenticate a request where the parameters need to be in the request body
/// </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);
/// <summary>
/// SHA256 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <param name="outputType">String type</param>
/// <returns></returns>
protected static string SignSHA256(string data, SignOutputType? outputType = null)
{ {
return parameters; using var encryptor = SHA256.Create();
var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes): BytesToHexString(resultBytes);
} }
/// <summary> /// <summary>
/// Add authentication to the header dictionary based on the provided credentials /// SHA384 sign the data and return the hash
/// </summary> /// </summary>
/// <param name="uri">The uri the request is for</param> /// <param name="data">Data to sign</param>
/// <param name="method">The HTTP method of the request</param> /// <param name="outputType">String type</param>
/// <param name="parameters">The provided parameters for the request</param> /// <returns></returns>
/// <param name="signed">Wether or not the request needs to be signed. If not typically the parameters list can just be returned</param> protected static string SignSHA384(string data, SignOutputType? outputType = null)
/// <param name="parameterPosition">Where post parameters are placed, in the URI or in the request body</param>
/// <param name="arraySerialization">How array parameters are serialized</param>
/// <returns>Should return a dictionary containing any header key/value pairs needed for authenticating the request</returns>
public virtual Dictionary<string, string> AddAuthenticationToHeaders(string uri, HttpMethod method, Dictionary<string, object> parameters, bool signed,
HttpMethodParameterPosition parameterPosition, ArrayParametersSerialization arraySerialization)
{ {
return new Dictionary<string, string>(); using var encryptor = SHA384.Create();
var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
/// <summary>
/// SHA512 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <param name="outputType">String type</param>
/// <returns></returns>
protected static string SignSHA512(string data, SignOutputType? outputType = null)
{
using var encryptor = SHA512.Create();
var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
/// <summary>
/// MD5 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <param name="outputType">String type</param>
/// <returns></returns>
protected static string SignMD5(string data, SignOutputType? outputType = null)
{
using var encryptor = MD5.Create();
var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
/// <summary>
/// HMACSHA256 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 SignHMACSHA256(string data, SignOutputType? outputType = null)
{
using var encryptor = new HMACSHA256(_sBytes);
var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
/// <summary>
/// HMACSHA384 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 SignHMACSHA384(string data, SignOutputType? outputType = null)
{
using var encryptor = new HMACSHA384(_sBytes);
var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
}
/// <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(string data, SignOutputType? outputType = null)
{
using var encryptor = new HMACSHA512(_sBytes);
var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
} }
/// <summary> /// <summary>
@ -76,16 +185,26 @@ namespace CryptoExchange.Net.Authentication
} }
/// <summary> /// <summary>
/// Convert byte array to hex /// Convert byte array to hex string
/// </summary> /// </summary>
/// <param name="buff"></param> /// <param name="buff"></param>
/// <returns></returns> /// <returns></returns>
protected static string ByteToString(byte[] buff) protected static string BytesToHexString(byte[] buff)
{ {
var result = string.Empty; var result = string.Empty;
foreach (var t in buff) foreach (var t in buff)
result += t.ToString("X2"); /* hex format */ result += t.ToString("X2");
return result; return result;
} }
/// <summary>
/// Convert byte array to base64 string
/// </summary>
/// <param name="buff"></param>
/// <returns></returns>
protected static string BytesToBase64String(byte[] buff)
{
return Convert.ToBase64String(buff);
}
} }
} }

View File

@ -0,0 +1,17 @@
namespace CryptoExchange.Net.Authentication
{
/// <summary>
/// Output string type
/// </summary>
public enum SignOutputType
{
/// <summary>
/// Hex string
/// </summary>
Hex,
/// <summary>
/// Base64 string
/// </summary>
Base64
}
}

View File

@ -83,9 +83,9 @@ namespace CryptoExchange.Net
ClientOptions = exchangeOptions; ClientOptions = exchangeOptions;
RequestFactory.Configure(exchangeOptions.RequestTimeout, exchangeOptions.Proxy, exchangeOptions.HttpClient); RequestFactory.Configure(exchangeOptions.RequestTimeout, exchangeOptions.Proxy, exchangeOptions.HttpClient);
} }
/// <summary> /// <summary>
/// Execute a request to the uri and deserialize the response into the provided type parameter /// Execute a request to the uri and deserialize the response into the provided type parameter
/// </summary> /// </summary>
@ -118,6 +118,11 @@ namespace CryptoExchange.Net
) where T : class ) where T : class
{ {
var requestId = NextId(); var requestId = NextId();
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)
{ {
@ -145,6 +150,9 @@ namespace CryptoExchange.Net
paramString += " with headers " + string.Join(", ", headers.Select(h => h.Key + $"=[{string.Join(",", h.Value)}]")); paramString += " with headers " + string.Join(", ", headers.Select(h => h.Key + $"=[{string.Join(",", h.Value)}]"));
} }
apiClient.TotalRequestsMade++;
TotalRequestsMade++;
log.Write(LogLevel.Debug, $"[{requestId}] Sending {method}{(signed ? " signed" : "")} request to {request.Uri}{paramString ?? " "}{(ClientOptions.Proxy == null ? "" : $" via proxy {ClientOptions.Proxy.Host}")}"); log.Write(LogLevel.Debug, $"[{requestId}] Sending {method}{(signed ? " signed" : "")} request to {request.Uri}{paramString ?? " "}{(ClientOptions.Proxy == null ? "" : $" via proxy {ClientOptions.Proxy.Host}")}");
return await GetResponseAsync<T>(request, deserializer, cancellationToken).ConfigureAwait(false); return await GetResponseAsync<T>(request, deserializer, cancellationToken).ConfigureAwait(false);
} }
@ -160,7 +168,6 @@ namespace CryptoExchange.Net
{ {
try try
{ {
TotalRequestsMade++;
var sw = Stopwatch.StartNew(); var sw = Stopwatch.StartNew();
var response = await request.GetResponseAsync(cancellationToken).ConfigureAwait(false); var response = await request.GetResponseAsync(cancellationToken).ConfigureAwait(false);
sw.Stop(); sw.Stop();
@ -278,22 +285,43 @@ namespace CryptoExchange.Net
int requestId, int requestId,
Dictionary<string, string>? additionalHeaders) Dictionary<string, string>? additionalHeaders)
{ {
parameters ??= new Dictionary<string, object>(); SortedDictionary<string, object> sortedParameters = new SortedDictionary<string, object>(GetParameterComparer());
if (parameters != null)
sortedParameters = new SortedDictionary<string, object>(parameters, GetParameterComparer());
var uriString = uri.ToString(); if (parameterPosition == HttpMethodParameterPosition.InUri)
if (apiClient.AuthenticationProvider != null) {
parameters = apiClient.AuthenticationProvider.AddAuthenticationToParameters(uriString, method, parameters, signed, parameterPosition, arraySerialization); foreach (var parameter in sortedParameters)
uri = uri.AddQueryParmeter(parameter.Key, parameter.Value.ToString());
if (parameterPosition == HttpMethodParameterPosition.InUri && parameters?.Any() == true) }
uriString += "?" + parameters.CreateParamString(true, arraySerialization);
var contentType = requestBodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader;
var request = RequestFactory.Create(method, uriString, requestId);
request.Accept = Constants.JsonContentHeader;
var length = sortedParameters.Count;
var headers = new Dictionary<string, string>(); var headers = new Dictionary<string, string>();
if (apiClient.AuthenticationProvider != null) if (apiClient.AuthenticationProvider != null)
headers = apiClient.AuthenticationProvider.AddAuthenticationToHeaders(uriString, method, parameters!, signed, parameterPosition, arraySerialization); {
if(parameterPosition == HttpMethodParameterPosition.InUri)
apiClient.AuthenticationProvider.AuthenticateUriRequest(apiClient, ref 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)
{
var uriBuilder = new UriBuilder();
uriBuilder.Scheme = uri.Scheme;
uriBuilder.Host = uri.Host;
uriBuilder.Path = uri.AbsolutePath;
uri = uriBuilder.Uri;
foreach(var parameter in sortedParameters)
uri = uri.AddQueryParmeter(parameter.Key, parameter.Value.ToString());
}
}
var request = RequestFactory.Create(method, uri, requestId);
request.Accept = Constants.JsonContentHeader;
foreach (var header in headers) foreach (var header in headers)
request.AddHeader(header.Key, header.Value); request.AddHeader(header.Key, header.Value);
@ -314,45 +342,78 @@ namespace CryptoExchange.Net
if (parameterPosition == HttpMethodParameterPosition.InBody) if (parameterPosition == HttpMethodParameterPosition.InBody)
{ {
if (parameters?.Any() == true) var contentType = requestBodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader;
WriteParamBody(request, parameters, contentType); if (sortedParameters?.Any() == true)
WriteParamBody(request, sortedParameters, contentType);
else else
request.SetContent(requestBodyEmptyContent, contentType); request.SetContent(requestBodyEmptyContent, contentType);
} }
return request; return request;
//var uriString = uri.ToString();
//if (apiClient.AuthenticationProvider != null)
// parameters = apiClient.AuthenticationProvider.AddAuthenticationToParameters(uriString, method, parameters, signed, parameterPosition, arraySerialization);
//if (parameterPosition == HttpMethodParameterPosition.InUri && parameters?.Any() == true)
// uriString += "?" + parameters.CreateParamString(true, arraySerialization);
//var contentType = requestBodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader;
//var request = RequestFactory.Create(method, uriString, requestId);
//request.Accept = Constants.JsonContentHeader;
//var headers = new Dictionary<string, string>();
//if (apiClient.AuthenticationProvider != null)
// headers = apiClient.AuthenticationProvider.AddAuthenticationToHeaders(uriString, method, parameters!, signed, parameterPosition, arraySerialization);
//foreach (var header in headers)
// request.AddHeader(header.Key, header.Value);
//if (additionalHeaders != null)
//{
// foreach (var header in additionalHeaders)
// request.AddHeader(header.Key, header.Value);
//}
//if(StandardRequestHeaders != null)
//{
// foreach (var header in StandardRequestHeaders)
// // Only add it if it isn't overwritten
// if(additionalHeaders?.ContainsKey(header.Key) != true)
// request.AddHeader(header.Key, header.Value);
//}
//if (parameterPosition == HttpMethodParameterPosition.InBody)
//{
// if (parameters?.Any() == true)
// WriteParamBody(request, parameters, contentType);
// else
// request.SetContent(requestBodyEmptyContent, contentType);
//}
//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>
/// <param name="request">The request to set the parameters on</param> /// <param name="request">The request to set the parameters on</param>
/// <param name="parameters">The parameters to set</param> /// <param name="parameters">The parameters to set</param>
/// <param name="contentType">The content type of the data</param> /// <param name="contentType">The content type of the data</param>
protected virtual void WriteParamBody(IRequest request, Dictionary<string, object> parameters, string contentType) protected virtual void WriteParamBody(IRequest request, SortedDictionary<string, object> parameters, string contentType)
{ {
if (requestBodyFormat == RequestBodyFormat.Json) if (requestBodyFormat == RequestBodyFormat.Json)
{ {
// Write the parameters as json in the body // Write the parameters as json in the body
var stringData = JsonConvert.SerializeObject(parameters.OrderBy(p => p.Key).ToDictionary(p => p.Key, p => p.Value)); var stringData = JsonConvert.SerializeObject(parameters);
request.SetContent(stringData, contentType); request.SetContent(stringData, contentType);
} }
else if (requestBodyFormat == RequestBodyFormat.FormData) else if (requestBodyFormat == RequestBodyFormat.FormData)
{ {
// Write the parameters as form data in the body // Write the parameters as form data in the body
var formData = HttpUtility.ParseQueryString(string.Empty); var stringData = parameters.ToFormData();
foreach (var kvp in parameters.OrderBy(p => p.Key))
{
if (kvp.Value.GetType().IsArray)
{
var array = (Array)kvp.Value;
foreach (var value in array)
formData.Add(kvp.Key, value.ToString());
}
else
formData.Add(kvp.Key, kvp.Value.ToString());
}
var stringData = formData.ToString();
request.SetContent(stringData, contentType); request.SetContent(stringData, contentType);
} }
} }

View File

@ -1,6 +1,11 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Logging;
using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects;
using Microsoft.Extensions.Logging;
namespace CryptoExchange.Net namespace CryptoExchange.Net
{ {
@ -9,16 +14,27 @@ namespace CryptoExchange.Net
/// </summary> /// </summary>
public abstract class RestApiClient: BaseApiClient public abstract class RestApiClient: BaseApiClient
{ {
protected abstract TimeSyncModel GetTimeSyncParameters();
protected abstract void UpdateTimeOffset(TimeSpan offset);
public abstract TimeSpan GetTimeOffset();
/// <summary>
/// Total amount of requests made with this API client
/// </summary>
public int TotalRequestsMade { get; set; }
/// <summary> /// <summary>
/// Options for this client /// Options for this client
/// </summary> /// </summary>
internal RestApiClientOptions Options { get; } public RestApiClientOptions Options { get; }
/// <summary> /// <summary>
/// List of rate limiters /// List of rate limiters
/// </summary> /// </summary>
internal IEnumerable<IRateLimiter> RateLimiters { get; } internal IEnumerable<IRateLimiter> RateLimiters { get; }
private Log _log;
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
@ -34,5 +50,59 @@ namespace CryptoExchange.Net
RateLimiters = rateLimiters; RateLimiters = rateLimiters;
} }
/// <summary>
/// Retrieve the server time for the purpose of syncing time between client and server to prevent authentication issues
/// </summary>
/// <returns>Server time</returns>
protected abstract Task<WebCallResult<DateTime>> GetServerTimestampAsync();
internal async Task<WebCallResult<bool>> SyncTimeAsync()
{
var timeSyncParams = GetTimeSyncParameters();
if (await timeSyncParams.Semaphore.WaitAsync(0).ConfigureAwait(false))
{
if (!timeSyncParams.SyncTime || (DateTime.UtcNow - timeSyncParams.LastSyncTime < TimeSpan.FromHours(1)))
{
timeSyncParams.Semaphore.Release();
return new WebCallResult<bool>(null, null, true, null);
}
var localTime = DateTime.UtcNow;
var result = await GetServerTimestampAsync().ConfigureAwait(false);
if (!result)
{
timeSyncParams.Semaphore.Release();
return result.As(false);
}
if (TotalRequestsMade == 1)
{
// If this was the first request make another one to calculate the offset since the first one can be slower
localTime = DateTime.UtcNow;
result = await GetServerTimestampAsync().ConfigureAwait(false);
if (!result)
{
timeSyncParams.Semaphore.Release();
return result.As(false);
}
}
// Calculate time offset between local and server
var offset = result.Data - localTime;
if (offset.TotalMilliseconds >= 0 && offset.TotalMilliseconds < 500)
{
// Small offset, probably mainly due to ping. Don't adjust time
UpdateTimeOffset(offset);
timeSyncParams.Semaphore.Release();
}
else
{
UpdateTimeOffset(offset);
timeSyncParams.Semaphore.Release();
}
}
return new WebCallResult<bool>(null, null, true, null);
}
} }
} }

View File

@ -5,6 +5,7 @@ using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security; using System.Security;
using System.Text; using System.Text;
using System.Web;
using CryptoExchange.Net.Logging; using CryptoExchange.Net.Logging;
using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -142,6 +143,49 @@ namespace CryptoExchange.Net
return uriString; return uriString;
} }
/// <summary>
/// Convert a dictionary to formdata string
/// </summary>
/// <param name="parameters"></param>
/// <returns></returns>
public static string ToFormData(this SortedDictionary<string, object> parameters)
{
var formData = HttpUtility.ParseQueryString(string.Empty);
foreach (var kvp in parameters.OrderBy(p => p.Key))
{
if (kvp.Value.GetType().IsArray)
{
var array = (Array)kvp.Value;
foreach (var value in array)
formData.Add(kvp.Key, value.ToString());
}
else
formData.Add(kvp.Key, kvp.Value.ToString());
}
return formData.ToString();
}
/// <summary>
/// Add parameter to URI
/// </summary>
/// <param name="uri"></param>
/// <param name="name"></param>
/// <param name="value"></param>
/// <returns></returns>
public static Uri AddQueryParmeter(this Uri uri, string name, string value)
{
var httpValueCollection = HttpUtility.ParseQueryString(uri.Query);
httpValueCollection.Remove(name);
httpValueCollection.Add(name, value);
var ub = new UriBuilder(uri);
ub.Query = httpValueCollection.ToString();
return ub.Uri;
}
/// <summary> /// <summary>
/// Get the string the secure string is representing /// Get the string the secure string is representing
/// </summary> /// </summary>

View File

@ -16,7 +16,7 @@ namespace CryptoExchange.Net.Interfaces
/// <param name="uri"></param> /// <param name="uri"></param>
/// <param name="requestId"></param> /// <param name="requestId"></param>
/// <returns></returns> /// <returns></returns>
IRequest Create(HttpMethod method, string uri, int requestId); IRequest Create(HttpMethod method, Uri uri, int requestId);
/// <summary> /// <summary>
/// Configure the requests created by this factory /// Configure the requests created by this factory

View File

@ -275,6 +275,11 @@ namespace CryptoExchange.Net.Objects
/// </summary> /// </summary>
public RateLimitingBehaviour RateLimitingBehaviour { get; set; } = RateLimitingBehaviour.Wait; public RateLimitingBehaviour RateLimitingBehaviour { get; set; } = RateLimitingBehaviour.Wait;
/// <summary>
/// Whether or not to automatically sync the local time with the server time
/// </summary>
public bool AutoTimestamp { get; set; } = true;
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
@ -312,12 +317,13 @@ namespace CryptoExchange.Net.Objects
if(def.RateLimiters != null) if(def.RateLimiters != null)
input.RateLimiters = def.RateLimiters.ToList(); input.RateLimiters = def.RateLimiters.ToList();
input.RateLimitingBehaviour = def.RateLimitingBehaviour; input.RateLimitingBehaviour = def.RateLimitingBehaviour;
input.AutoTimestamp = def.AutoTimestamp;
} }
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() public override string ToString()
{ {
return $"{base.ToString()}, RateLimiters: {RateLimiters?.Count}, RateLimitBehaviour: {RateLimitingBehaviour}"; return $"{base.ToString()}, RateLimiters: {RateLimiters?.Count}, RateLimitBehaviour: {RateLimitingBehaviour}, AutoTimestamp: {AutoTimestamp}";
} }
} }

View File

@ -0,0 +1,21 @@
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

@ -36,7 +36,7 @@ namespace CryptoExchange.Net.Requests
} }
/// <inheritdoc /> /// <inheritdoc />
public IRequest Create(HttpMethod method, string uri, int requestId) public IRequest Create(HttpMethod method, Uri uri, int requestId)
{ {
if (httpClient == null) if (httpClient == null)
throw new InvalidOperationException("Cant create request before configuring http client"); throw new InvalidOperationException("Cant create request before configuring http client");