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; _intermediateType = default; } } /// /// 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("ProtobufError: " + 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; _intermediateType = default; } } }