From 66fb10498374d03bd3241da5926ca8298ddea7f6 Mon Sep 17 00:00:00 2001 From: Jkorf Date: Mon, 16 Jun 2025 15:12:03 +0200 Subject: [PATCH] wip --- .../Protobuf/ProtobufMessageAccessor.cs | 489 ++++++++++++++++++ .../Protobuf/ProtobufMessageSerializer.cs | 49 ++ .../CryptoExchange.Net.Protobuf.csproj | 47 ++ .../CryptoExchange.Net.Protobuf.xml | 123 +++++ CryptoExchange.Net.Protobuf/README.md | 7 + .../Protobuf/ProtobufMessageAccessor.cs | 308 ----------- .../Protobuf/ProtobufMessageSerializer.cs | 29 -- .../SystemTextJson/DecimalConverter.cs | 17 +- CryptoExchange.Net/ExchangeHelpers.cs | 24 + .../Interfaces/IMessageSerializer.cs | 11 +- .../Sockets/SocketConnection.cs | 4 +- 11 files changed, 751 insertions(+), 357 deletions(-) create mode 100644 CryptoExchange.Net.Protobuf/Converters/Protobuf/ProtobufMessageAccessor.cs create mode 100644 CryptoExchange.Net.Protobuf/Converters/Protobuf/ProtobufMessageSerializer.cs create mode 100644 CryptoExchange.Net.Protobuf/CryptoExchange.Net.Protobuf.csproj create mode 100644 CryptoExchange.Net.Protobuf/CryptoExchange.Net.Protobuf.xml create mode 100644 CryptoExchange.Net.Protobuf/README.md delete mode 100644 CryptoExchange.Net/Converters/Protobuf/ProtobufMessageAccessor.cs delete mode 100644 CryptoExchange.Net/Converters/Protobuf/ProtobufMessageSerializer.cs diff --git a/CryptoExchange.Net.Protobuf/Converters/Protobuf/ProtobufMessageAccessor.cs b/CryptoExchange.Net.Protobuf/Converters/Protobuf/ProtobufMessageAccessor.cs new file mode 100644 index 0000000..d497428 --- /dev/null +++ b/CryptoExchange.Net.Protobuf/Converters/Protobuf/ProtobufMessageAccessor.cs @@ -0,0 +1,489 @@ +using CryptoExchange.Net.Converters.MessageParsing; +using CryptoExchange.Net.Interfaces; +using CryptoExchange.Net.Objects; +using ProtoBuf; +using ProtoBuf.Meta; +using System; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Runtime.InteropServices.ComTypes; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.Converters.Protobuf +{ + /// + /// System.Text.Json message accessor + /// +#if NET5_0_OR_GREATER + public abstract class ProtobufMessageAccessor< + [DynamicallyAccessedMembers( +#if NET8_0_OR_GREATER + DynamicallyAccessedMemberTypes.NonPublicConstructors | + DynamicallyAccessedMemberTypes.PublicFields | + DynamicallyAccessedMemberTypes.NonPublicFields | + DynamicallyAccessedMemberTypes.NonPublicNestedTypes | + DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.NonPublicProperties | +#endif + DynamicallyAccessedMemberTypes.PublicNestedTypes | + DynamicallyAccessedMemberTypes.NonPublicMethods | + DynamicallyAccessedMemberTypes.PublicMethods + )] + TIntermediateType> : IMessageAccessor +#else + public abstract class ProtobufMessageAccessor : IMessageAccessor +#endif + { + /// + /// The intermediate deserialization object + /// + protected TIntermediateType? _intermediateType; + + /// + public bool IsValid { get; set; } + + /// + public abstract bool OriginalDataAvailable { get; } + + /// + public object? Underlying => throw new NotImplementedException(); + + /// + /// ctor + /// + public ProtobufMessageAccessor() + { + } + + /// + public NodeType? GetNodeType() + { + throw new Exception(""); + } + + /// + public NodeType? GetNodeType(MessagePath path) + { + if (_intermediateType == null) + throw new InvalidOperationException("Data not read"); + + object? value = _intermediateType; + foreach (var step in path) + { + if (value == null) + break; + + if (step.Type == 0) + { + // array index + } + else if (step.Type == 1) + { + // property value + value = value.GetType().GetProperty(step.Property!)?.GetValue(value); + } + else + { + // property name + } + } + + if (value == null) + return null; + + var valueType = value.GetType(); + if (valueType.IsArray) + return NodeType.Array; + + if (IsSimple(valueType)) + return NodeType.Value; + + return NodeType.Object; + } + + private static bool IsSimple(Type type) + { + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + // nullable type, check if the nested type is simple. + return IsSimple(type.GetGenericArguments()[0]); + } + return type.IsPrimitive + || type.IsEnum + || type == typeof(string) + || type == typeof(decimal); + } + + + /// + public T? GetValue(MessagePath path) + { + if (_intermediateType == null) + throw new InvalidOperationException("Data not read"); + + object? value = _intermediateType; + foreach(var step in path) + { + if (value == null) + break; + + if (step.Type == 0) + { + // array index + } + else if (step.Type == 1) + { + // property value + value = value.GetType().GetProperty(step.Property!)?.GetValue(value); + } + else + { + // property name + } + } + + return (T?)value; + } + + /// + public T?[]? GetValues(MessagePath path) + { + throw new Exception(""); + + } + + /// + public abstract string GetOriginalString(); + + /// + public abstract void Clear(); + + /// + public abstract CallResult Deserialize( +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers( +#if NET8_0_OR_GREATER + DynamicallyAccessedMemberTypes.NonPublicConstructors | + DynamicallyAccessedMemberTypes.PublicFields | + DynamicallyAccessedMemberTypes.NonPublicFields | + DynamicallyAccessedMemberTypes.NonPublicNestedTypes | + DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.NonPublicProperties | + DynamicallyAccessedMemberTypes.PublicConstructors | +#endif + DynamicallyAccessedMemberTypes.PublicNestedTypes | + DynamicallyAccessedMemberTypes.NonPublicMethods | + DynamicallyAccessedMemberTypes.PublicMethods + )] +#endif + Type type, MessagePath? path = null); + + /// + public abstract CallResult Deserialize< + +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers( +#if NET8_0_OR_GREATER + DynamicallyAccessedMemberTypes.NonPublicConstructors | + DynamicallyAccessedMemberTypes.PublicFields | + DynamicallyAccessedMemberTypes.NonPublicFields | + DynamicallyAccessedMemberTypes.NonPublicNestedTypes | + DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.NonPublicProperties | + DynamicallyAccessedMemberTypes.PublicConstructors | +#endif + DynamicallyAccessedMemberTypes.PublicNestedTypes | + DynamicallyAccessedMemberTypes.NonPublicMethods | + DynamicallyAccessedMemberTypes.PublicMethods + )] +#endif + T>(MessagePath? path = null); + } + + /// + /// System.Text.Json stream message accessor + /// + public class ProtobufStreamMessageAccessor< +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers( +#if NET8_0_OR_GREATER + DynamicallyAccessedMemberTypes.NonPublicConstructors | + DynamicallyAccessedMemberTypes.PublicFields | + DynamicallyAccessedMemberTypes.NonPublicFields | + DynamicallyAccessedMemberTypes.NonPublicNestedTypes | + DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.NonPublicProperties | + DynamicallyAccessedMemberTypes.PublicConstructors | +#endif + DynamicallyAccessedMemberTypes.PublicNestedTypes | + DynamicallyAccessedMemberTypes.NonPublicMethods | + DynamicallyAccessedMemberTypes.PublicMethods + )] +#endif + TIntermediate> : ProtobufMessageAccessor, IStreamMessageAccessor + { + private Stream? _stream; + + /// + public override bool OriginalDataAvailable => _stream?.CanSeek == true; + + /// + /// ctor + /// + public ProtobufStreamMessageAccessor(): base() + { + } + + /// + public override CallResult Deserialize( +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers( +#if NET8_0_OR_GREATER + DynamicallyAccessedMemberTypes.NonPublicConstructors | + DynamicallyAccessedMemberTypes.PublicFields | + DynamicallyAccessedMemberTypes.NonPublicFields | + DynamicallyAccessedMemberTypes.NonPublicNestedTypes | + DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.NonPublicProperties | + DynamicallyAccessedMemberTypes.PublicConstructors | +#endif + DynamicallyAccessedMemberTypes.PublicNestedTypes | + DynamicallyAccessedMemberTypes.NonPublicMethods | + DynamicallyAccessedMemberTypes.PublicMethods + )] +#endif + Type type, MessagePath? path = null) + { + try + { + var result = Serializer.Deserialize(type, _stream); + return new CallResult(result); + } + catch (Exception ex) + { + return new CallResult(new DeserializeError(ex.Message)); + } + } + + /// + public override CallResult Deserialize< +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers( +#if NET8_0_OR_GREATER + DynamicallyAccessedMemberTypes.NonPublicConstructors | + DynamicallyAccessedMemberTypes.PublicFields | + DynamicallyAccessedMemberTypes.NonPublicFields | + DynamicallyAccessedMemberTypes.NonPublicNestedTypes | + DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.NonPublicProperties | + DynamicallyAccessedMemberTypes.PublicConstructors | +#endif + DynamicallyAccessedMemberTypes.PublicNestedTypes | + DynamicallyAccessedMemberTypes.NonPublicMethods | + DynamicallyAccessedMemberTypes.PublicMethods + )] +#endif + T>(MessagePath? path = null) + { + try + { + var result = Serializer.Deserialize(_stream); + return new CallResult(result); + } + catch(Exception ex) + { + return new CallResult(new DeserializeError(ex.Message)); + } + } + + /// + public Task Read(Stream stream, bool bufferStream) + { + if (bufferStream && stream is not MemoryStream) + { + // We need to be buffer the stream, and it's not currently a seekable stream, so copy it to a new memory stream + _stream = new MemoryStream(); + stream.CopyTo(_stream); + _stream.Position = 0; + } + else if (bufferStream) + { + // We need to buffer the stream, and the current stream is seekable, store as is + _stream = stream; + } + else + { + // We don't need to buffer the stream, so don't bother keeping the reference + } + + try + { + _intermediateType = Serializer.Deserialize(_stream); + IsValid = true; + return Task.FromResult(CallResult.SuccessResult); + } + catch (Exception ex) + { + // Not a json message + IsValid = false; + return Task.FromResult(new CallResult(new DeserializeError("JsonError: " + ex.Message, ex))); + } + } + + /// + public override string GetOriginalString() + { + if (_stream is null) + throw new NullReferenceException("Stream not initialized"); + + _stream.Position = 0; + using var textReader = new StreamReader(_stream, Encoding.UTF8, false, 1024, true); + return textReader.ReadToEnd(); + } + + /// + public override void Clear() + { + _stream?.Dispose(); + _stream = null; + } + + } + + /// + /// Protobuf byte message accessor + /// + public class ProtobufByteMessageAccessor< +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers( +#if NET8_0_OR_GREATER + DynamicallyAccessedMemberTypes.NonPublicConstructors | + DynamicallyAccessedMemberTypes.PublicFields | + DynamicallyAccessedMemberTypes.NonPublicFields | + DynamicallyAccessedMemberTypes.NonPublicNestedTypes | + DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.NonPublicProperties | + DynamicallyAccessedMemberTypes.PublicConstructors | +#endif + DynamicallyAccessedMemberTypes.PublicNestedTypes | + DynamicallyAccessedMemberTypes.NonPublicMethods | + DynamicallyAccessedMemberTypes.PublicMethods + )] +#endif + TIntermediate> : ProtobufMessageAccessor, IByteMessageAccessor + { + private ReadOnlyMemory _bytes; + + /// + /// ctor + /// + public ProtobufByteMessageAccessor() : base() + { + } + + /// + public override CallResult Deserialize( +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers( +#if NET8_0_OR_GREATER + DynamicallyAccessedMemberTypes.NonPublicConstructors | + DynamicallyAccessedMemberTypes.PublicFields | + DynamicallyAccessedMemberTypes.NonPublicFields | + DynamicallyAccessedMemberTypes.NonPublicNestedTypes | + DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.NonPublicProperties | + DynamicallyAccessedMemberTypes.PublicConstructors | +#endif + DynamicallyAccessedMemberTypes.PublicNestedTypes | + DynamicallyAccessedMemberTypes.NonPublicMethods | + DynamicallyAccessedMemberTypes.PublicMethods + )] +#endif + Type type, MessagePath? path = null) + { + try + { + using var stream = new MemoryStream(_bytes.ToArray()); + stream.Position = 0; + var result = Serializer.Deserialize(type, stream); + return new CallResult(result); + } + catch (Exception ex) + { + return new CallResult(new DeserializeError(ex.Message)); + } + } + + /// +#if NET5_0_OR_GREATER + public override CallResult Deserialize< + [DynamicallyAccessedMembers( +#if NET8_0_OR_GREATER + DynamicallyAccessedMemberTypes.NonPublicConstructors | + DynamicallyAccessedMemberTypes.PublicFields | + DynamicallyAccessedMemberTypes.NonPublicFields | + DynamicallyAccessedMemberTypes.NonPublicNestedTypes | + DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.NonPublicProperties | + DynamicallyAccessedMemberTypes.PublicConstructors | +#endif + DynamicallyAccessedMemberTypes.PublicNestedTypes | + DynamicallyAccessedMemberTypes.NonPublicMethods | + DynamicallyAccessedMemberTypes.PublicMethods + )] + T>(MessagePath? path = null) +#else + public override CallResult Deserialize(MessagePath? path = null) +#endif + { + try + { + var result = Serializer.Deserialize(_bytes); + return new CallResult(result); + } + catch (Exception ex) + { + return new CallResult(new DeserializeError(ex.Message)); + } + } + + /// + public CallResult Read(ReadOnlyMemory data) + { + _bytes = data; + + try + { + _intermediateType = Serializer.Deserialize(data); + IsValid = true; + return CallResult.SuccessResult; + } + catch (Exception ex) + { + // Not a json message + IsValid = false; + return new CallResult(new DeserializeError("JsonError: " + ex.Message, ex)); + } + } + + /// + public override string GetOriginalString() => + // NetStandard 2.0 doesn't support GetString from a ReadonlySpan, so use ToArray there instead +#if NETSTANDARD2_0 + Encoding.UTF8.GetString(_bytes.ToArray()); +#else + Encoding.UTF8.GetString(_bytes.Span); +#endif + + /// + public override bool OriginalDataAvailable => true; + + /// + public override void Clear() + { + _bytes = null; + } + } +} \ No newline at end of file diff --git a/CryptoExchange.Net.Protobuf/Converters/Protobuf/ProtobufMessageSerializer.cs b/CryptoExchange.Net.Protobuf/Converters/Protobuf/ProtobufMessageSerializer.cs new file mode 100644 index 0000000..93c0deb --- /dev/null +++ b/CryptoExchange.Net.Protobuf/Converters/Protobuf/ProtobufMessageSerializer.cs @@ -0,0 +1,49 @@ +using CryptoExchange.Net.Interfaces; +using ProtoBuf.Meta; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Reflection; + +namespace CryptoExchange.Net.Converters.Protobuf +{ + /// + public class ProtobufMessageSerializer : IByteMessageSerializer + { + private readonly RuntimeTypeModel _model = RuntimeTypeModel.Create("CryptoExchange"); + + /// + /// ctor + /// + public ProtobufMessageSerializer() + { + _model.UseImplicitZeroDefaults = false; + } + + /// +#if NET5_0_OR_GREATER + public byte[] Serialize< + [DynamicallyAccessedMembers( +#if NET8_0_OR_GREATER + DynamicallyAccessedMemberTypes.NonPublicConstructors | + DynamicallyAccessedMemberTypes.PublicFields | + DynamicallyAccessedMemberTypes.NonPublicFields | + DynamicallyAccessedMemberTypes.NonPublicNestedTypes | + DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.NonPublicProperties | + DynamicallyAccessedMemberTypes.PublicConstructors | +#endif + DynamicallyAccessedMemberTypes.PublicNestedTypes | + DynamicallyAccessedMemberTypes.NonPublicMethods | + DynamicallyAccessedMemberTypes.PublicMethods + )] + T>(T message) +#else + public byte[] Serialize(T message) +#endif + { + using var memoryStream = new MemoryStream(); + _model.Serialize(memoryStream, message); + return memoryStream.ToArray(); + } + } +} diff --git a/CryptoExchange.Net.Protobuf/CryptoExchange.Net.Protobuf.csproj b/CryptoExchange.Net.Protobuf/CryptoExchange.Net.Protobuf.csproj new file mode 100644 index 0000000..24cecd1 --- /dev/null +++ b/CryptoExchange.Net.Protobuf/CryptoExchange.Net.Protobuf.csproj @@ -0,0 +1,47 @@ + + + + netstandard2.0;netstandard2.1;net8.0;net9.0 + + + + CryptoExchange.Net.Protobuf + JKorf + Protobuf support for CryptoExchange.Net + 9.1.0 + 9.1.0 + 9.1.0 + false + CryptoExchange;CryptoExchange.Net + git + https://github.com/JKorf/CryptoExchange.Net.git + https://github.com/JKorf/CryptoExchange.Net/tree/master/CryptoExchange.Net.Protobuf + en + README.md + icon.png + true + https://github.com/JKorf/CryptoExchange.Net?tab=readme-ov-file#release-notes + enable + 12.0 + MIT + + + + + + + true + true + snupkg + true + true + + + CryptoExchange.Net.Protobuf.xml + + + + + + + diff --git a/CryptoExchange.Net.Protobuf/CryptoExchange.Net.Protobuf.xml b/CryptoExchange.Net.Protobuf/CryptoExchange.Net.Protobuf.xml new file mode 100644 index 0000000..f5da907 --- /dev/null +++ b/CryptoExchange.Net.Protobuf/CryptoExchange.Net.Protobuf.xml @@ -0,0 +1,123 @@ + + + + CryptoExchange.Net.Protobuf + + + + + System.Text.Json message accessor + + + + + The intermediate deserialization object + + + + + + + + + + + + + + ctor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + System.Text.Json stream message accessor + + + + + + + + ctor + + + + + + + + + + + + + + + + + + + + Protobuf byte message accessor + + + + + ctor + + + + + + + + + + + + + + + + + + + + + + + + + + ctor + + + + + + + diff --git a/CryptoExchange.Net.Protobuf/README.md b/CryptoExchange.Net.Protobuf/README.md new file mode 100644 index 0000000..35cec8f --- /dev/null +++ b/CryptoExchange.Net.Protobuf/README.md @@ -0,0 +1,7 @@ +# ![.CryptoExchange.Net](https://github.com/JKorf/CryptoExchange.Net/blob/ffcb7db8ff597c2f14982d68464015a748815580/CryptoExchange.Net/Icon/icon.png) CryptoExchange.Net.Proto + +[![.NET](https://img.shields.io/github/actions/workflow/status/JKorf/CryptoExchange.Net.Protobuf/dotnet.yml?style=for-the-badge)](https://github.com/JKorf/CryptoExchange.NetProtobuf/actions/workflows/dotnet.yml) [![Nuget downloads](https://img.shields.io/nuget/dt/CryptoExchange.NetProtobuf.svg?style=for-the-badge)](https://www.nuget.org/packages/CryptoExchange.NetProtobuf) ![License](https://img.shields.io/github/license/JKorf/CryptoExchange.Net?style=for-the-badge) + +Protobuf support for CryptoExchange.Net. + +## Release notes \ No newline at end of file diff --git a/CryptoExchange.Net/Converters/Protobuf/ProtobufMessageAccessor.cs b/CryptoExchange.Net/Converters/Protobuf/ProtobufMessageAccessor.cs deleted file mode 100644 index 8dc2bc2..0000000 --- a/CryptoExchange.Net/Converters/Protobuf/ProtobufMessageAccessor.cs +++ /dev/null @@ -1,308 +0,0 @@ -using CryptoExchange.Net.Converters.MessageParsing; -using CryptoExchange.Net.Interfaces; -using CryptoExchange.Net.Objects; -using ProtoBuf; -using System; -using System.IO; -using System.Runtime.InteropServices.ComTypes; -using System.Text; -using System.Text.Json; -using System.Threading.Tasks; - -namespace CryptoExchange.Net.Converters.Protobuf -{ - /// - /// System.Text.Json message accessor - /// - public abstract class ProtobufMessageAccessor : IMessageAccessor - { - protected T? _intermediateType; - - /// - public bool IsValid { get; set; } - - /// - public abstract bool OriginalDataAvailable { get; } - - /// - public object? Underlying => throw new NotImplementedException(); - - /// - /// ctor - /// - public ProtobufMessageAccessor() - { - } - - /// - public NodeType? GetNodeType() - { - throw new Exception(""); - } - - /// - public NodeType? GetNodeType(MessagePath path) - { - object value = _intermediateType; - foreach (var step in path) - { - if (step.Type == 0) - { - // array index - } - else if (step.Type == 1) - { - // property value - value = value.GetType().GetProperty(step.Property).GetValue(value); - } - else - { - // property name - } - } - - var valueType = value.GetType(); - if (valueType.IsArray) - return NodeType.Array; - - if (IsSimple(valueType)) - return NodeType.Value; - - return NodeType.Object; - } - - private static bool IsSimple(Type type) - { - if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) - { - // nullable type, check if the nested type is simple. - return IsSimple(type.GetGenericArguments()[0]); - } - return type.IsPrimitive - || type.IsEnum - || type == typeof(string) - || type == typeof(decimal); - } - - - /// - public T? GetValue(MessagePath path) - { - object value = _intermediateType; - foreach(var step in path) - { - if (step.Type == 0) - { - // array index - } - else if (step.Type == 1) - { - // property value - value = value.GetType().GetProperty(step.Property)?.GetValue(value); - } - else - { - // property name - } - } - - return (T?)value; - } - - /// - public T?[]? GetValues(MessagePath path) - { - throw new Exception(""); - - } - - /// - public abstract string GetOriginalString(); - - /// - public abstract void Clear(); - - public abstract CallResult Deserialize(Type type, MessagePath? path = null); - public abstract CallResult Deserialize(MessagePath? path = null); - } - - /// - /// System.Text.Json stream message accessor - /// - public class ProtobufStreamMessageAccessor : ProtobufMessageAccessor, IStreamMessageAccessor - { - private Stream? _stream; - - /// - public override bool OriginalDataAvailable => _stream?.CanSeek == true; - - /// - /// ctor - /// - public ProtobufStreamMessageAccessor(): base() - { - } - - /// - public override CallResult Deserialize(Type type, MessagePath? path = null) - { - try - { - var result = Serializer.Deserialize(type, _stream); - return new CallResult(result); - } - catch (Exception ex) - { - return new CallResult(new DeserializeError(ex.Message)); - } - } - - /// - public override CallResult Deserialize(MessagePath? path = null) - { - try - { - var result = Serializer.Deserialize(_stream); - return new CallResult(result); - } - catch(Exception ex) - { - return new CallResult(new DeserializeError(ex.Message)); - } - } - - /// - public async Task Read(Stream stream, bool bufferStream) - { - if (bufferStream && stream is not MemoryStream) - { - // We need to be buffer the stream, and it's not currently a seekable stream, so copy it to a new memory stream - _stream = new MemoryStream(); - stream.CopyTo(_stream); - _stream.Position = 0; - } - else if (bufferStream) - { - // We need to buffer the stream, and the current stream is seekable, store as is - _stream = stream; - } - else - { - // We don't need to buffer the stream, so don't bother keeping the reference - } - - try - { - _intermediateType = Serializer.Deserialize(_stream); - IsValid = true; - return CallResult.SuccessResult; - } - catch (Exception ex) - { - // Not a json message - IsValid = false; - return new CallResult(new DeserializeError("JsonError: " + ex.Message, ex)); - } - } - - /// - public override string GetOriginalString() - { - if (_stream is null) - throw new NullReferenceException("Stream not initialized"); - - _stream.Position = 0; - using var textReader = new StreamReader(_stream, Encoding.UTF8, false, 1024, true); - return textReader.ReadToEnd(); - } - - /// - public override void Clear() - { - _stream?.Dispose(); - _stream = null; - } - - } - - /// - /// Protobuf byte message accessor - /// - public class ProtobufByteMessageAccessor : ProtobufMessageAccessor, IByteMessageAccessor - { - private ReadOnlyMemory _bytes; - - /// - /// ctor - /// - public ProtobufByteMessageAccessor() : base() - { - } - - /// - public override CallResult Deserialize(Type type, MessagePath? path = null) - { - try - { - using var stream = new MemoryStream(_bytes.ToArray()); - var result = Serializer.Deserialize(type, stream); - return new CallResult(result); - } - catch (Exception ex) - { - return new CallResult(new DeserializeError(ex.Message)); - } - } - - /// - public override CallResult Deserialize(MessagePath? path = null) - { - try - { - var result = Serializer.Deserialize(_bytes); - return new CallResult(result); - } - catch (Exception ex) - { - return new CallResult(new DeserializeError(ex.Message)); - } - } - - /// - public CallResult Read(ReadOnlyMemory data) - { - _bytes = data; - - try - { - _intermediateType = Serializer.Deserialize(data); - IsValid = true; - return CallResult.SuccessResult; - } - catch (Exception ex) - { - // Not a json message - IsValid = false; - return new CallResult(new DeserializeError("JsonError: " + ex.Message, ex)); - } - } - - /// - public override string GetOriginalString() => - // NetStandard 2.0 doesn't support GetString from a ReadonlySpan, so use ToArray there instead -#if NETSTANDARD2_0 - Encoding.UTF8.GetString(_bytes.ToArray()); -#else - Encoding.UTF8.GetString(_bytes.Span); -#endif - - /// - public override bool OriginalDataAvailable => true; - - /// - public override void Clear() - { - _bytes = null; - } - } -} \ No newline at end of file diff --git a/CryptoExchange.Net/Converters/Protobuf/ProtobufMessageSerializer.cs b/CryptoExchange.Net/Converters/Protobuf/ProtobufMessageSerializer.cs deleted file mode 100644 index 466cb2c..0000000 --- a/CryptoExchange.Net/Converters/Protobuf/ProtobufMessageSerializer.cs +++ /dev/null @@ -1,29 +0,0 @@ -using CryptoExchange.Net.Interfaces; -using ProtoBuf.Meta; -using System.IO; -using System.Reflection; - -namespace CryptoExchange.Net.Converters.Protobuf -{ - /// - public class ProtobufMessageSerializer : IByteMessageSerializer - { - private readonly RuntimeTypeModel _model = RuntimeTypeModel.Create("CryptoExchange"); - - /// - /// ctor - /// - public ProtobufMessageSerializer() - { - _model.UseImplicitZeroDefaults = false; - } - - /// - public byte[] Serialize(T message) - { - using var memoryStream = new MemoryStream(); - _model.Serialize(memoryStream, message); - return memoryStream.ToArray(); - } - } -} diff --git a/CryptoExchange.Net/Converters/SystemTextJson/DecimalConverter.cs b/CryptoExchange.Net/Converters/SystemTextJson/DecimalConverter.cs index 7067a77..5d7b694 100644 --- a/CryptoExchange.Net/Converters/SystemTextJson/DecimalConverter.cs +++ b/CryptoExchange.Net/Converters/SystemTextJson/DecimalConverter.cs @@ -19,22 +19,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson if (reader.TokenType == JsonTokenType.String) { var value = reader.GetString(); - if (string.IsNullOrEmpty(value) || string.Equals("null", value, StringComparison.OrdinalIgnoreCase)) - return null; - - if (string.Equals("Infinity", value, StringComparison.Ordinal)) - // Infinity returned by the server, default to max value - return decimal.MaxValue; - - try - { - return decimal.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); - } - catch(OverflowException) - { - // Value doesn't fit decimal, default to max value - return decimal.MaxValue; - } + return ExchangeHelpers.ParseDecimal(value); } try diff --git a/CryptoExchange.Net/ExchangeHelpers.cs b/CryptoExchange.Net/ExchangeHelpers.cs index aeb0737..29a5408 100644 --- a/CryptoExchange.Net/ExchangeHelpers.cs +++ b/CryptoExchange.Net/ExchangeHelpers.cs @@ -2,6 +2,7 @@ using CryptoExchange.Net.SharedApis; using System; using System.Collections.Generic; +using System.Globalization; using System.Runtime.CompilerServices; using System.Security.Cryptography; using System.Threading; @@ -341,5 +342,28 @@ namespace CryptoExchange.Net adjustedQuantity = symbol.QuantityDecimals.HasValue ? (minNotionalAdjust ? RoundUp(adjustedQuantity, symbol.QuantityDecimals.Value) : RoundDown(adjustedQuantity, symbol.QuantityDecimals.Value)) : adjustedQuantity; } + + /// + /// Parse a decimal value from a string + /// + public static decimal? ParseDecimal(string? value) + { + if (string.IsNullOrEmpty(value) || string.Equals("null", value, StringComparison.OrdinalIgnoreCase)) + return null; + + if (string.Equals("Infinity", value, StringComparison.Ordinal)) + // Infinity returned by the server, default to max value + return decimal.MaxValue; + + try + { + return decimal.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture); + } + catch (OverflowException) + { + // Value doesn't fit decimal, default to max value + return decimal.MaxValue; + } + } } } diff --git a/CryptoExchange.Net/Interfaces/IMessageSerializer.cs b/CryptoExchange.Net/Interfaces/IMessageSerializer.cs index 80af4b7..01009a8 100644 --- a/CryptoExchange.Net/Interfaces/IMessageSerializer.cs +++ b/CryptoExchange.Net/Interfaces/IMessageSerializer.cs @@ -1,4 +1,6 @@ -namespace CryptoExchange.Net.Interfaces +using System.Diagnostics.CodeAnalysis; + +namespace CryptoExchange.Net.Interfaces { /// /// Serializer interface @@ -7,6 +9,9 @@ { } + /// + /// Serialize to byte array + /// public interface IByteMessageSerializer: IMessageSerializer { /// @@ -17,7 +22,9 @@ byte[] Serialize(T message); } - + /// + /// Serialize to string + /// public interface IStringMessageSerializer: IMessageSerializer { /// diff --git a/CryptoExchange.Net/Sockets/SocketConnection.cs b/CryptoExchange.Net/Sockets/SocketConnection.cs index 993cabd..3e2a92f 100644 --- a/CryptoExchange.Net/Sockets/SocketConnection.cs +++ b/CryptoExchange.Net/Sockets/SocketConnection.cs @@ -211,8 +211,8 @@ namespace CryptoExchange.Net.Sockets private SocketStatus _status; private readonly IMessageSerializer _serializer; - private IByteMessageAccessor _stringMessageAccessor; - private IByteMessageAccessor _byteMessageAccessor; + private IByteMessageAccessor? _stringMessageAccessor; + private IByteMessageAccessor? _byteMessageAccessor; /// /// The task that is sending periodic data on the websocket. Can be used for sending Ping messages every x seconds or similar. Not necessary.