diff --git a/src/BootstrapBlazor/Extensions/ITcpSocketClientExtensions.cs b/src/BootstrapBlazor/Extensions/ITcpSocketClientExtensions.cs index cf58a2b31ad..9fadf2ab442 100644 --- a/src/BootstrapBlazor/Extensions/ITcpSocketClientExtensions.cs +++ b/src/BootstrapBlazor/Extensions/ITcpSocketClientExtensions.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. // Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone +using System.Reflection; using System.Runtime.Versioning; using System.Text; @@ -76,20 +77,18 @@ public static void SetDataPackageAdapter(this ITcpSocketClient client, IDataPack } /// - /// Configures the specified to use a custom data package adapter and a callback - /// function for processing received data. + /// Configures the specified to use a data package adapter and a callback function + /// for processing received data. /// - /// This method sets up the to use the provided for handling incoming data. The adapter processes the raw data received by the client and - /// attempts to convert it into an instance of . If the conversion is successful, the - /// is invoked with the converted entity; otherwise, it is invoked with . - /// The type of the entity that the data package adapter will attempt to convert the received data into. - /// The instance to configure. - /// The instance responsible for handling and processing incoming data. - /// A callback function to be invoked with the processed data of type . The callback - /// receives if the data cannot be converted to . - public static void SetDataPackageAdapter(this ITcpSocketClient client, IDataPackageAdapter adapter, Func callback) + /// This method sets up the to process incoming data using the + /// specified and . The is called with the converted entity whenever data is received. + /// The type of the entity that the data will be converted to. + /// The TCP socket client to configure. + /// The data package adapter responsible for handling incoming data. + /// The converter used to transform the received data into the specified entity type. + /// The callback function to be invoked with the converted entity. + public static void SetDataPackageAdapter(this ITcpSocketClient client, IDataPackageAdapter adapter, ISocketDataConverter socketDataConverter, Func callback) { // 设置 ITcpSocketClient 的回调函数 client.ReceivedCallBack = async buffer => @@ -102,14 +101,57 @@ public static void SetDataPackageAdapter(this ITcpSocketClient client, adapter.ReceivedCallBack = async buffer => { TEntity? ret = default; - if (adapter.TryConvertTo(buffer, out var t)) + if (socketDataConverter.TryConvertTo(buffer, out var t)) { - if (t is TEntity entity) - { - ret = entity; - } + ret = t; } await callback(ret); }; } + + /// + /// Configures the specified to use a custom data package adapter and callback + /// function. + /// + /// This method sets up the to use the specified for handling incoming data. If the type is decorated with a , the associated converter is used to transform the data before invoking + /// the . The callback is called with the converted entity or if + /// conversion fails. + /// The type of entity that the data package adapter will handle. + /// The TCP socket client to configure. + /// The data package adapter responsible for processing incoming data. + /// The callback function to invoke with the processed entity of type . + public static void SetDataPackageAdapter(this ITcpSocketClient client, IDataPackageAdapter adapter, Func callback) + { + // 设置 ITcpSocketClient 的回调函数 + client.ReceivedCallBack = async buffer => + { + // 将接收到的数据传递给 DataPackageAdapter 进行数据处理合规数据触发 ReceivedCallBack 回调 + await adapter.HandlerAsync(buffer); + }; + + var type = typeof(TEntity); + var converterType = type.GetCustomAttribute(); + if (converterType is { Type: not null }) + { + if (Activator.CreateInstance(converterType.Type) is ISocketDataConverter socketDataConverter) + { + // 设置 DataPackageAdapter 的回调函数 + adapter.ReceivedCallBack = async buffer => + { + TEntity? ret = default; + if (socketDataConverter.TryConvertTo(buffer, out var t)) + { + ret = t; + } + await callback(ret); + }; + } + } + else + { + adapter.ReceivedCallBack = async buffer => await callback(default); + } + } } diff --git a/src/BootstrapBlazor/Services/TcpSocket/DataConverter/ISocketDataConverter.cs b/src/BootstrapBlazor/Services/TcpSocket/DataConverter/ISocketDataConverter.cs new file mode 100644 index 00000000000..1d5f3f6b0d7 --- /dev/null +++ b/src/BootstrapBlazor/Services/TcpSocket/DataConverter/ISocketDataConverter.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +namespace BootstrapBlazor.Components; + +/// +/// Defines a method to convert raw socket data into a specified entity type. +/// +/// The type of entity to convert the data into. +public interface ISocketDataConverter +{ + /// + /// Attempts to convert the specified data to an instance of . + /// + /// This method does not throw an exception if the conversion fails. Instead, it returns and sets to . + /// The data to be converted, represented as a read-only memory block of bytes. + /// When this method returns, contains the converted if the conversion succeeded; + /// otherwise, . + /// if the conversion was successful; otherwise, . + bool TryConvertTo(ReadOnlyMemory data, [NotNullWhen(true)] out TEntity? entity); +} diff --git a/src/BootstrapBlazor/Services/TcpSocket/DataConverter/SocketDataConverterAttribute.cs b/src/BootstrapBlazor/Services/TcpSocket/DataConverter/SocketDataConverterAttribute.cs new file mode 100644 index 00000000000..4a44475bc53 --- /dev/null +++ b/src/BootstrapBlazor/Services/TcpSocket/DataConverter/SocketDataConverterAttribute.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +namespace BootstrapBlazor.Components; + +/// +/// +/// +[AttributeUsage(AttributeTargets.Class)] +public class SocketDataConverterAttribute : Attribute +{ + /// + /// Gets or sets the type of the . + /// + public Type? Type { get; set; } +} diff --git a/src/BootstrapBlazor/Services/TcpSocket/DataConverter/SocketDataConverterBase.cs b/src/BootstrapBlazor/Services/TcpSocket/DataConverter/SocketDataConverterBase.cs new file mode 100644 index 00000000000..b4b7b971cc4 --- /dev/null +++ b/src/BootstrapBlazor/Services/TcpSocket/DataConverter/SocketDataConverterBase.cs @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +using System.Reflection; + +namespace BootstrapBlazor.Components; + +/// +/// Provides a base class for converting socket data into a specified entity type. +/// +/// The type of entity to convert the socket data into. +public abstract class SocketDataConverterBase : ISocketDataConverter +{ + /// + /// + /// + /// + /// + /// + public abstract bool TryConvertTo(ReadOnlyMemory data, [NotNullWhen(true)] out TEntity? entity); + + /// + /// 将字节数据转换为指定实体类型的实例。 + /// + /// + /// + protected virtual bool Parse(ReadOnlyMemory data, TEntity entity) + { + // 使用 SocketDataFieldAttribute 特性获取数据转换规则 + var ret = false; + if (entity != null) + { + var properties = entity.GetType().GetProperties().Where(p => p.CanWrite).ToList(); + foreach (var p in properties) + { + var attr = p.GetCustomAttribute(false); + if (attr != null) + { + p.SetValue(entity, attr.ConvertTo(data)); + } + } + ret = true; + } + return ret; + } +} diff --git a/src/BootstrapBlazor/Services/TcpSocket/DataPackageAdapter/DataPackageAdapter.cs b/src/BootstrapBlazor/Services/TcpSocket/DataPackageAdapter/DataPackageAdapter.cs index a1b7e98e940..77733014514 100644 --- a/src/BootstrapBlazor/Services/TcpSocket/DataPackageAdapter/DataPackageAdapter.cs +++ b/src/BootstrapBlazor/Services/TcpSocket/DataPackageAdapter/DataPackageAdapter.cs @@ -48,12 +48,18 @@ public virtual async ValueTask HandlerAsync(ReadOnlyMemory data, Cancellat /// /// /// + /// /// /// - public virtual bool TryConvertTo(ReadOnlyMemory data, [NotNullWhen(true)] out object? entity) + public virtual bool TryConvertTo(ReadOnlyMemory data, ISocketDataConverter socketDataConverter, out TEntity? entity) { - entity = null; - return false; + entity = default; + var ret = socketDataConverter.TryConvertTo(data, out var v); + if (ret) + { + entity = v; + } + return ret; } /// diff --git a/src/BootstrapBlazor/Services/TcpSocket/DataPackageAdapter/IDataPackageAdapter.cs b/src/BootstrapBlazor/Services/TcpSocket/DataPackageAdapter/IDataPackageAdapter.cs index f603c528460..e37d9272495 100644 --- a/src/BootstrapBlazor/Services/TcpSocket/DataPackageAdapter/IDataPackageAdapter.cs +++ b/src/BootstrapBlazor/Services/TcpSocket/DataPackageAdapter/IDataPackageAdapter.cs @@ -41,13 +41,15 @@ public interface IDataPackageAdapter ValueTask HandlerAsync(ReadOnlyMemory data, CancellationToken token = default); /// - /// Attempts to convert the specified binary data into an object representation. + /// Attempts to convert the specified byte data into an entity of type . /// - /// This method does not throw an exception if the conversion fails. Instead, it returns and sets to . - /// The binary data to be converted. Must not be empty. - /// When this method returns , contains the converted object. When this method returns , contains . + /// This method does not throw an exception if the conversion fails. Instead, it returns and sets to its default value. + /// The type of the entity to convert the data to. + /// The byte data to be converted. + /// The converter used to transform the byte data into an entity. + /// When this method returns, contains the converted entity if the conversion was successful; otherwise, the default + /// value for the type of the entity. /// if the conversion was successful; otherwise, . - bool TryConvertTo(ReadOnlyMemory data, [NotNullWhen(true)] out object? entity); + bool TryConvertTo(ReadOnlyMemory data, ISocketDataConverter socketDataConverter, out TEntity? entity); } diff --git a/src/BootstrapBlazor/Services/TcpSocket/Extensions/SocketDataPropertyExtensions.cs b/src/BootstrapBlazor/Services/TcpSocket/Extensions/SocketDataPropertyExtensions.cs new file mode 100644 index 00000000000..f24d5e02580 --- /dev/null +++ b/src/BootstrapBlazor/Services/TcpSocket/Extensions/SocketDataPropertyExtensions.cs @@ -0,0 +1,106 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +namespace BootstrapBlazor.Components; + +static class SocketDataPropertyExtensions +{ + public static ISocketDataPropertyConverter? GetConverter(this SocketDataPropertyAttribute attribute) + { + return attribute.GetConverterByType() ?? attribute.GetDefaultConverter(); + } + + private static ISocketDataPropertyConverter? GetConverterByType(this SocketDataPropertyAttribute attribute) + { + ISocketDataPropertyConverter? converter = null; + var converterType = attribute.ConverterType; + if (converterType != null) + { + var converterParameters = attribute.ConverterParameters; + var c = Activator.CreateInstance(converterType, converterParameters); + if(c is ISocketDataPropertyConverter v) + { + converter = v; + } + } + return converter; + } + + private static ISocketDataPropertyConverter? GetDefaultConverter(this SocketDataPropertyAttribute attribute) + { + ISocketDataPropertyConverter? converter = null; + var type = attribute.Type; + if (type != null) + { + if (type == typeof(byte[])) + { + converter = new SocketDataByteArrayConverter(); + } + else if (type == typeof(string)) + { + converter = new SocketDataStringConverter(attribute.EncodingName); + } + else if (type.IsEnum) + { + converter = new SocketDataEnumConverter(attribute.Type); + } + else if (type == typeof(bool)) + { + converter = new SocketDataBoolConverter(); + } + else if (type == typeof(short)) + { + converter = new SocketDataInt16BigEndianConverter(); + } + else if (type == typeof(int)) + { + converter = new SocketDataInt32BigEndianConverter(); + } + else if (type == typeof(long)) + { + converter = new SocketDataInt64BigEndianConverter(); + } + else if (type == typeof(float)) + { + converter = new SocketDataSingleBigEndianConverter(); + } + else if (type == typeof(double)) + { + converter = new SocketDataDoubleBigEndianConverter(); + } + else if (type == typeof(ushort)) + { + converter = new SocketDataUInt16BigEndianConverter(); + } + else if (type == typeof(uint)) + { + converter = new SocketDataUInt32BigEndianConverter(); + } + else if (type == typeof(ulong)) + { + converter = new SocketDataUInt64BigEndianConverter(); + } + } + return converter; + } + + public static object? ConvertTo(this SocketDataPropertyAttribute attribute, ReadOnlyMemory data) + { + object? ret = null; + var start = attribute.Offset; + var length = attribute.Length; + + if (data.Length >= start + length) + { + var buffer = data.Slice(start, length); + var converter = attribute.GetConverter(); + if (converter != null) + { + ret = converter.Convert(buffer); + } + } + return ret; + } +} diff --git a/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/ISocketDataPropertyConverter.cs b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/ISocketDataPropertyConverter.cs new file mode 100644 index 00000000000..b49b0a2a383 --- /dev/null +++ b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/ISocketDataPropertyConverter.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +namespace BootstrapBlazor.Components; + +/// +/// Socket 数据转换器接口 +/// +public interface ISocketDataPropertyConverter +{ + /// + /// 将数据转换为指定类型的对象 + /// + /// + /// + object? Convert(ReadOnlyMemory data); +} diff --git a/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataBoolConverter.cs b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataBoolConverter.cs new file mode 100644 index 00000000000..de8d2162e84 --- /dev/null +++ b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataBoolConverter.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 bool 数据转换器 +/// +public class SocketDataBoolConverter : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + var ret = false; + if (data.Length == 1) + { + ret = data.Span[0] != 0x00; + } + return ret; + } +} diff --git a/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataByteArrayConverter.cs b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataByteArrayConverter.cs new file mode 100644 index 00000000000..cea9b606969 --- /dev/null +++ b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataByteArrayConverter.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 byte[] 数组转换器 +/// +public class SocketDataByteArrayConverter : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + return data.ToArray(); + } +} diff --git a/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataDoubleBigEndianConverter.cs b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataDoubleBigEndianConverter.cs new file mode 100644 index 00000000000..89a806c699a --- /dev/null +++ b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataDoubleBigEndianConverter.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +using System.Buffers.Binary; + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 double 数据大端转换器 +/// +public class SocketDataDoubleBigEndianConverter : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + double ret = 0; + Span paddedSpan = stackalloc byte[8]; + data.Span.CopyTo(paddedSpan[(8 - data.Length)..]); + if (BinaryPrimitives.TryReadDoubleBigEndian(paddedSpan, out var v)) + { + ret = v; + } + return ret; + } +} diff --git a/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataDoubleLittleEndianConverter.cs b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataDoubleLittleEndianConverter.cs new file mode 100644 index 00000000000..fd2f36cd2ee --- /dev/null +++ b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataDoubleLittleEndianConverter.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +using System.Buffers.Binary; + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 double 数据小端转换器 +/// +public class SocketDataDoubleLittleEndianConverter : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + double ret = 0; + Span paddedSpan = stackalloc byte[8]; + data.Span.CopyTo(paddedSpan[(8 - data.Length)..]); + if (BinaryPrimitives.TryReadDoubleLittleEndian(paddedSpan, out var v)) + { + ret = v; + } + return ret; + } +} diff --git a/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataEnumConverter.cs b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataEnumConverter.cs new file mode 100644 index 00000000000..80aec33cc4c --- /dev/null +++ b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataEnumConverter.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 Enum 数据转换器 +/// +public class SocketDataEnumConverter(Type? type) : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + object? ret = null; + if (type != null) + { + if (data.Length == 1) + { + var v = data.Span[0]; + if (Enum.TryParse(type, v.ToString(), out var enumValue)) + { + ret = enumValue; + } + } + } + return ret; + } +} diff --git a/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataInt16BigEndianConverter.cs b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataInt16BigEndianConverter.cs new file mode 100644 index 00000000000..ca3d50fd112 --- /dev/null +++ b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataInt16BigEndianConverter.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +using System.Buffers.Binary; + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 short 数据大端转换器 +/// +public class SocketDataInt16BigEndianConverter : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + short ret = 0; + Span paddedSpan = stackalloc byte[2]; + data.Span.CopyTo(paddedSpan[(2 - data.Length)..]); + if (BinaryPrimitives.TryReadInt16BigEndian(paddedSpan, out var v)) + { + ret = v; + } + return ret; + } +} diff --git a/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataInt16LittleEndianConverter.cs b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataInt16LittleEndianConverter.cs new file mode 100644 index 00000000000..11025eabe63 --- /dev/null +++ b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataInt16LittleEndianConverter.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +using System.Buffers.Binary; + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 short 数据小端转换器 +/// +public class SocketDataInt16LittleEndianConverter : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + short ret = 0; + Span paddedSpan = stackalloc byte[2]; + data.Span.CopyTo(paddedSpan[(2 - data.Length)..]); + if (BinaryPrimitives.TryReadInt16LittleEndian(paddedSpan, out var v)) + { + ret = v; + } + return ret; + } +} diff --git a/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataInt32BigEndianConverter.cs b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataInt32BigEndianConverter.cs new file mode 100644 index 00000000000..3d02f4cbd33 --- /dev/null +++ b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataInt32BigEndianConverter.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +using System.Buffers.Binary; + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 int 数据大端转换器 +/// +public class SocketDataInt32BigEndianConverter : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + var ret = 0; + Span paddedSpan = stackalloc byte[4]; + data.Span.CopyTo(paddedSpan[(4 - data.Length)..]); + + if (BinaryPrimitives.TryReadInt32BigEndian(paddedSpan, out var v)) + { + ret = v; + } + return ret; + } +} diff --git a/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataInt32LittleEndianConverter.cs b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataInt32LittleEndianConverter.cs new file mode 100644 index 00000000000..7e0be3f5d1d --- /dev/null +++ b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataInt32LittleEndianConverter.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +using System.Buffers.Binary; + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 int 数据小端转换器 +/// +public class SocketDataInt32LittleEndianConverter : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + var ret = 0; + Span paddedSpan = stackalloc byte[4]; + data.Span.CopyTo(paddedSpan[(4 - data.Length)..]); + + if (BinaryPrimitives.TryReadInt32LittleEndian(paddedSpan, out var v)) + { + ret = v; + } + return ret; + } +} diff --git a/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataInt64BigEndianConverter.cs b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataInt64BigEndianConverter.cs new file mode 100644 index 00000000000..1c5ffb0fa48 --- /dev/null +++ b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataInt64BigEndianConverter.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +using System.Buffers.Binary; + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 long 数据大端转换器 +/// +public class SocketDataInt64BigEndianConverter : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + long ret = 0; + Span paddedSpan = stackalloc byte[8]; + data.Span.CopyTo(paddedSpan[(8 - data.Length)..]); + + if (BinaryPrimitives.TryReadInt64BigEndian(paddedSpan, out var v)) + { + ret = v; + } + return ret; + } +} diff --git a/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataInt64LittleEndianConverter.cs b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataInt64LittleEndianConverter.cs new file mode 100644 index 00000000000..91e920d23cd --- /dev/null +++ b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataInt64LittleEndianConverter.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +using System.Buffers.Binary; + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 long 数据小端转换器 +/// +public class SocketDataInt64LittleEndianConverter : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + long ret = 0; + Span paddedSpan = stackalloc byte[8]; + data.Span.CopyTo(paddedSpan[(8 - data.Length)..]); + + if (BinaryPrimitives.TryReadInt64LittleEndian(paddedSpan, out var v)) + { + ret = v; + } + return ret; + } +} diff --git a/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataPropertyAttribute.cs b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataPropertyAttribute.cs new file mode 100644 index 00000000000..f766b404f14 --- /dev/null +++ b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataPropertyAttribute.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +namespace BootstrapBlazor.Components; + +/// +/// Represents an attribute used to mark a field as a socket data field. +/// +/// This attribute can be applied to fields to indicate that they are part of the data transmitted over a +/// socket connection. It is intended for use in scenarios where socket communication requires specific fields to be +/// identified for processing. +[AttributeUsage(AttributeTargets.Property)] +public class SocketDataPropertyAttribute : Attribute +{ + /// + /// 获得/设置 数据类型 + /// + public Type? Type { get; set; } + + /// + /// 获得/设置 数据偏移量 + /// + public int Offset { get; set; } + + /// + /// 获得/设置 数据长度 + /// + public int Length { get; set; } + + /// + /// 获得/设置 数据编码名称 + /// + public string? EncodingName { get; set; } + + /// + /// 获得/设置 数据转换器类型 + /// + public Type? ConverterType { get; set; } + + /// + /// 获得/设置 数据转换器构造函数参数 + /// + public object?[]? ConverterParameters { get; set; } +} diff --git a/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataSingleBigEndianConverter.cs b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataSingleBigEndianConverter.cs new file mode 100644 index 00000000000..233040aa894 --- /dev/null +++ b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataSingleBigEndianConverter.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +using System.Buffers.Binary; + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 float 数据大端转换器 +/// +public class SocketDataSingleBigEndianConverter : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + float ret = 0; + Span paddedSpan = stackalloc byte[4]; + data.Span.CopyTo(paddedSpan[(4 - data.Length)..]); + if (BinaryPrimitives.TryReadSingleBigEndian(paddedSpan, out var v)) + { + ret = v; + } + return ret; + } +} diff --git a/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataSingleLittleEndianConverter.cs b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataSingleLittleEndianConverter.cs new file mode 100644 index 00000000000..7858af38904 --- /dev/null +++ b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataSingleLittleEndianConverter.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +using System.Buffers.Binary; + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 float 数据小端转换器 +/// +public class SocketDataSingleLittleEndianConverter : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + float ret = 0; + Span paddedSpan = stackalloc byte[4]; + data.Span.CopyTo(paddedSpan[(4 - data.Length)..]); + if (BinaryPrimitives.TryReadSingleLittleEndian(paddedSpan, out var v)) + { + ret = v; + } + return ret; + } +} diff --git a/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataStringConverter.cs b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataStringConverter.cs new file mode 100644 index 00000000000..9b87b5577e0 --- /dev/null +++ b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataStringConverter.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +using System.Text; + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 string 数据转换器 +/// +public class SocketDataStringConverter(string? encodingName) : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + var encoding = string.IsNullOrEmpty(encodingName) ? Encoding.UTF8 : Encoding.GetEncoding(encodingName); + return encoding.GetString(data.Span); + } +} diff --git a/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataUInt16BigEndianConverter.cs b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataUInt16BigEndianConverter.cs new file mode 100644 index 00000000000..cf5c7c793c7 --- /dev/null +++ b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataUInt16BigEndianConverter.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +using System.Buffers.Binary; + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 ushort 数据大端转换器 +/// +public class SocketDataUInt16BigEndianConverter : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + ushort ret = 0; + Span paddedSpan = stackalloc byte[2]; + data.Span.CopyTo(paddedSpan[(2 - data.Length)..]); + if (BinaryPrimitives.TryReadUInt16BigEndian(paddedSpan, out var v)) + { + ret = v; + } + return ret; + } +} diff --git a/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataUInt16LittleEndianConverter.cs b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataUInt16LittleEndianConverter.cs new file mode 100644 index 00000000000..288e83db658 --- /dev/null +++ b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataUInt16LittleEndianConverter.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +using System.Buffers.Binary; + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 ushort 数据小端转换器 +/// +public class SocketDataUInt16LittleEndianConverter : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + ushort ret = 0; + Span paddedSpan = stackalloc byte[2]; + data.Span.CopyTo(paddedSpan[(2 - data.Length)..]); + if (BinaryPrimitives.TryReadUInt16LittleEndian(paddedSpan, out var v)) + { + ret = v; + } + return ret; + } +} diff --git a/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataUInt32BigEndianConverter.cs b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataUInt32BigEndianConverter.cs new file mode 100644 index 00000000000..c731501fad6 --- /dev/null +++ b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataUInt32BigEndianConverter.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +using System.Buffers.Binary; + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 uint 数据大端转换器 +/// +public class SocketDataUInt32BigEndianConverter : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + uint ret = 0; + Span paddedSpan = stackalloc byte[4]; + data.Span.CopyTo(paddedSpan[(4 - data.Length)..]); + if (BinaryPrimitives.TryReadUInt32BigEndian(paddedSpan, out var v)) + { + ret = v; + } + return ret; + } +} diff --git a/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataUInt32LittleEndianConverter.cs b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataUInt32LittleEndianConverter.cs new file mode 100644 index 00000000000..509edb7a02b --- /dev/null +++ b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataUInt32LittleEndianConverter.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +using System.Buffers.Binary; + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 uint 数据小端转换器 +/// +public class SocketDataUInt32LittleEndianConverter : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + uint ret = 0; + Span paddedSpan = stackalloc byte[4]; + data.Span.CopyTo(paddedSpan[(4 - data.Length)..]); + if (BinaryPrimitives.TryReadUInt32LittleEndian(paddedSpan, out var v)) + { + ret = v; + } + return ret; + } +} diff --git a/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataUInt64BigEndianConverter.cs b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataUInt64BigEndianConverter.cs new file mode 100644 index 00000000000..1003b1d3d19 --- /dev/null +++ b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataUInt64BigEndianConverter.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +using System.Buffers.Binary; + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 ulong 数据大端转换器 +/// +public class SocketDataUInt64BigEndianConverter : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + ulong ret = 0; + Span paddedSpan = stackalloc byte[8]; + data.Span.CopyTo(paddedSpan[(8 - data.Length)..]); + if (BinaryPrimitives.TryReadUInt64BigEndian(paddedSpan, out var v)) + { + ret = v; + } + return ret; + } +} diff --git a/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataUInt64LittleEndianConverter.cs b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataUInt64LittleEndianConverter.cs new file mode 100644 index 00000000000..533f4c35ef1 --- /dev/null +++ b/src/BootstrapBlazor/Services/TcpSocket/PropertyConverter/SocketDataUInt64LittleEndianConverter.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +using System.Buffers.Binary; + +namespace BootstrapBlazor.Components; + +/// +/// Sokcet 数据转换为 ulong 数据小端转换器 +/// +public class SocketDataUInt64LittleEndianConverter : ISocketDataPropertyConverter +{ + /// + /// + /// + /// + public object? Convert(ReadOnlyMemory data) + { + ulong ret = 0; + Span paddedSpan = stackalloc byte[8]; + data.Span.CopyTo(paddedSpan[(8 - data.Length)..]); + if (BinaryPrimitives.TryReadUInt64LittleEndian(paddedSpan, out var v)) + { + ret = v; + } + return ret; + } +} diff --git a/test/UnitTest/Services/TcpSocketFactoryTest.cs b/test/UnitTest/Services/TcpSocketFactoryTest.cs index b552daedf06..d9c977c9108 100644 --- a/test/UnitTest/Services/TcpSocketFactoryTest.cs +++ b/test/UnitTest/Services/TcpSocketFactoryTest.cs @@ -645,17 +645,17 @@ public async Task DelimiterDataPackageHandler_Ok() public async Task TryConvertTo_Ok() { var port = 8886; - var server = StartTcpServer(port, MockSplitPackageAsync); + var server = StartTcpServer(port, MockEntityPackageAsync); var client = CreateClient(); var tcs = new TaskCompletionSource(); MockEntity? entity = null; // 设置数据适配器 - var adapter = new MockEntityDataPackageAdapter + var adapter = new DataPackageAdapter { - DataPackageHandler = new FixLengthDataPackageHandler(7), + DataPackageHandler = new FixLengthDataPackageHandler(29), }; - client.SetDataPackageAdapter(adapter, t => + client.SetDataPackageAdapter(adapter, new MockEntitySocketDataConverter(), t => { entity = t; tcs.SetResult(); @@ -674,43 +674,83 @@ public async Task TryConvertTo_Ok() Assert.Equal([1, 2, 3, 4, 5], entity.Header); Assert.Equal([3, 4], entity.Body); - // 测试异常流程 - var adapter2 = new DataPackageAdapter(); - var result = adapter2.TryConvertTo(data, out var t); - Assert.False(result); - Assert.Null(t); - } + // string + Assert.Equal("1", entity.Value1); - [Fact] - public async Task TryConvertTo_Null() - { - var port = 8890; - var server = StartTcpServer(port, MockSplitPackageAsync); - var client = CreateClient(); - var tcs = new TaskCompletionSource(); - MockEntity? entity = null; + // string + Assert.Equal("1", entity.Value14); - // 设置数据适配器 - var adapter = new MockErrorEntityDataPackageAdapter - { - DataPackageHandler = new FixLengthDataPackageHandler(7), - }; + // int + Assert.Equal(9, entity.Value2); + + // long + Assert.Equal(16, entity.Value3); + + // double + Assert.Equal(3.14, entity.Value4); + + // single + Assert.NotEqual(0, entity.Value5); + + // short + Assert.Equal(0x23, entity.Value6); + + // ushort + Assert.Equal(0x24, entity.Value7); + + // uint + Assert.Equal((uint)0x25, entity.Value8); + + // ulong + Assert.Equal((ulong)0x26, entity.Value9); + + // bool + Assert.True(entity.Value10); + + // enum + Assert.Equal(EnumEducation.Middle, entity.Value11); + + // foo + Assert.NotNull(entity.Value12); + Assert.Equal(0x29, entity.Value12.Id); + Assert.Equal("test", entity.Value12.Name); + + // no attribute + Assert.Null(entity.Value13); + + // 测试 SocketDataConverter 标签功能 + tcs = new TaskCompletionSource(); client.SetDataPackageAdapter(adapter, t => { entity = t; tcs.SetResult(); return Task.CompletedTask; }); + await client.SendAsync(data); + await tcs.Task; - // 连接 TCP Server - var connect = await client.ConnectAsync("localhost", port); + Assert.NotNull(entity); + Assert.Equal([1, 2, 3, 4, 5], entity.Header); - // 发送数据 - var data = new ReadOnlyMemory([1, 2, 3, 4, 5]); + // 测试数据适配器直接调用 TryConvertTo 方法转换数据 + var adapter2 = new DataPackageAdapter(); + var result = adapter2.TryConvertTo(data, new MockEntitySocketDataConverter(), out var t); + Assert.True(result); + Assert.NotNull(t); + Assert.Equal([1, 2, 3, 4, 5], entity.Header); + + // 测试 SetDataPackageAdapter 泛型无标签情况 + tcs = new TaskCompletionSource(); + NoConvertEntity? noConvertEntity = null; + client.SetDataPackageAdapter(adapter, t => + { + noConvertEntity = t; + tcs.SetResult(); + return Task.CompletedTask; + }); await client.SendAsync(data); await tcs.Task; - - Assert.Null(entity); + Assert.Null(noConvertEntity); } private static TcpListener StartTcpServer(int port, Func handler) @@ -777,6 +817,23 @@ private static async Task MockSplitPackageAsync(TcpClient client) } } + private static async Task MockEntityPackageAsync(TcpClient client) + { + using var stream = client.GetStream(); + while (true) + { + var buffer = new byte[1024]; + var len = await stream.ReadAsync(buffer); + if (len == 0) + { + break; + } + + // 回写数据到客户端 + await stream.WriteAsync(new byte[] { 0x1, 0x2, 0x3, 0x4, 0x5, 0x3, 0x4, 0x31, 0x09, 0x10, 0x40, 0x09, 0x1E, 0xB8, 0x51, 0xEB, 0x85, 0x1F, 0x40, 0x49, 0x0F, 0xDB, 0x23, 0x24, 0x25, 0x26, 0x01, 0x01, 0x29 }, CancellationToken.None); + } + } + private static async Task MockStickyPackageAsync(TcpClient client) { using var stream = client.GetStream(); @@ -1080,29 +1137,77 @@ public void SetReceive(bool state) } } - class MockEntityDataPackageAdapter : DataPackageAdapter + class MockEntitySocketDataConverter : SocketDataConverterBase { - public override bool TryConvertTo(ReadOnlyMemory data, [NotNullWhen(true)] out object? entity) + public override bool TryConvertTo(ReadOnlyMemory data, [NotNullWhen(true)] out MockEntity? entity) { - entity = new MockEntity - { - Header = data[..5].ToArray(), - Body = data[5..].ToArray() - }; - return true; + var v = new MockEntity(); + var ret = Parse(data, v); + entity = ret ? v : null; + return ret; } } - class MockErrorEntityDataPackageAdapter : DataPackageAdapter + [SocketDataConverter(Type = typeof(MockEntitySocketDataConverter))] + class MockEntity { - public override bool TryConvertTo(ReadOnlyMemory data, [NotNullWhen(true)] out object? entity) + [SocketDataProperty(Type = typeof(byte[]), Offset = 0, Length = 5)] + public byte[]? Header { get; set; } + + [SocketDataProperty(Type = typeof(byte[]), Offset = 5, Length = 2)] + public byte[]? Body { get; set; } + + [SocketDataProperty(Type = typeof(string), Offset = 7, Length = 1, EncodingName = "utf-8")] + public string? Value1 { get; set; } + + [SocketDataProperty(Type = typeof(int), Offset = 8, Length = 1)] + public int Value2 { get; set; } + + [SocketDataProperty(Type = typeof(long), Offset = 9, Length = 1)] + public long Value3 { get; set; } + + [SocketDataProperty(Type = typeof(double), Offset = 10, Length = 8)] + public double Value4 { get; set; } + + [SocketDataProperty(Type = typeof(float), Offset = 18, Length = 4)] + public float Value5 { get; set; } + + [SocketDataProperty(Type = typeof(short), Offset = 22, Length = 1)] + public short Value6 { get; set; } + + [SocketDataProperty(Type = typeof(ushort), Offset = 23, Length = 1)] + public ushort Value7 { get; set; } + + [SocketDataProperty(Type = typeof(uint), Offset = 24, Length = 1)] + public uint Value8 { get; set; } + + [SocketDataProperty(Type = typeof(ulong), Offset = 25, Length = 1)] + public ulong Value9 { get; set; } + + [SocketDataProperty(Type = typeof(bool), Offset = 26, Length = 1)] + public bool Value10 { get; set; } + + [SocketDataProperty(Type = typeof(EnumEducation), Offset = 27, Length = 1)] + public EnumEducation Value11 { get; set; } + + [SocketDataProperty(Type = typeof(Foo), Offset = 28, Length = 1, ConverterType = typeof(FooConverter), ConverterParameters = ["test"])] + public Foo? Value12 { get; set; } + + [SocketDataProperty(Type = typeof(string), Offset = 7, Length = 1)] + public string? Value14 { get; set; } + + public string? Value13 { get; set; } + } + + class FooConverter(string name) : ISocketDataPropertyConverter + { + public object? Convert(ReadOnlyMemory data) { - entity = new Foo(); - return true; + return new Foo() { Id = data.Span[0], Name = name }; } } - class MockEntity + class NoConvertEntity { public byte[]? Header { get; set; } diff --git a/test/UnitTest/Services/TcpSocketPropertyConverterTest.cs b/test/UnitTest/Services/TcpSocketPropertyConverterTest.cs new file mode 100644 index 00000000000..60a63ab4533 --- /dev/null +++ b/test/UnitTest/Services/TcpSocketPropertyConverterTest.cs @@ -0,0 +1,73 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +namespace UnitTest.Services; + +public class TcpSocketPropertyConverterTest +{ + [Fact] + public void UInt16Converter_Ok() + { + var converter = new SocketDataUInt16LittleEndianConverter(); + var actual = converter.Convert(new byte[] { 0xFF, 0x00 }); + Assert.Equal((ushort)0xFF, actual); + } + + [Fact] + public void Int16Converter_Ok() + { + var converter = new SocketDataInt16LittleEndianConverter(); + var actual = converter.Convert(new byte[] { 0xFF, 0x00 }); + Assert.Equal((short)0xFF, actual); + } + + [Fact] + public void UInt32Converter_Ok() + { + var converter = new SocketDataUInt32LittleEndianConverter(); + var actual = converter.Convert(new byte[] { 0xFF, 0x00, 0x00, 0x00 }); + Assert.Equal((uint)0xFF, actual); + } + + [Fact] + public void Int32Converter_Ok() + { + var converter = new SocketDataInt32LittleEndianConverter(); + var actual = converter.Convert(new byte[] { 0xFF, 0x00, 0x00, 0x00 }); + Assert.Equal(0xFF, actual); + } + + [Fact] + public void UInt64Converter_Ok() + { + var converter = new SocketDataUInt64LittleEndianConverter(); + var actual = converter.Convert(new byte[] { 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); + Assert.Equal((ulong)0xFF, actual); + } + + [Fact] + public void Int64Converter_Ok() + { + var converter = new SocketDataInt64LittleEndianConverter(); + var actual = converter.Convert(new byte[] { 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); + Assert.Equal((long)0xFF, actual); + } + + [Fact] + public void SingleConverter_Ok() + { + var converter = new SocketDataSingleLittleEndianConverter(); + var actual = converter.Convert(new byte[] { 0xC3, 0xF5, 0x48, 0x40 }); + Assert.Equal((float)3.14, actual); + } + + [Fact] + public void DoubleConverter_Ok() + { + var converter = new SocketDataDoubleLittleEndianConverter(); + var actual = converter.Convert(new byte[] { 0x1F, 0x85, 0xEB, 0x51, 0xB8, 0x1E, 0x09, 0x40 }); + Assert.Equal((double)3.14, actual); + } +}