feat(ab-transport): project skeleton with AbStatus, CIP reply parsing and the ICipChannel seam
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests;
|
||||
|
||||
public sealed class AbStatusCodesTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData((byte)0x00, AbStatus.Ok)]
|
||||
[InlineData((byte)0x04, AbStatus.NotFound)]
|
||||
[InlineData((byte)0x05, AbStatus.NotFound)]
|
||||
[InlineData((byte)0x16, AbStatus.NotFound)]
|
||||
[InlineData((byte)0x06, AbStatus.Partial)]
|
||||
[InlineData((byte)0x08, AbStatus.Unsupported)]
|
||||
[InlineData((byte)0x0F, AbStatus.NotAllowed)]
|
||||
[InlineData((byte)0x10, AbStatus.DeviceStateConflict)]
|
||||
[InlineData((byte)0x13, AbStatus.TooSmall)]
|
||||
[InlineData((byte)0x15, AbStatus.TooLarge)]
|
||||
[InlineData((byte)0x0B, AbStatus.Ok)]
|
||||
[InlineData((byte)0x0A, AbStatus.OutOfBounds)]
|
||||
[InlineData((byte)0x0C, AbStatus.DeviceStateConflict)]
|
||||
[InlineData((byte)0x0E, AbStatus.NotAllowed)]
|
||||
[InlineData((byte)0x1E, AbStatus.BadReply)]
|
||||
[InlineData((byte)0x01, AbStatus.BadReply)]
|
||||
public void Cip_general_status_maps(byte general, AbStatus expected) =>
|
||||
AbStatusCodes.FromCip(general, ReadOnlySpan<ushort>.Empty).ShouldBe(expected);
|
||||
|
||||
[Theory]
|
||||
[InlineData((ushort)0x2105, AbStatus.OutOfBounds)]
|
||||
[InlineData((ushort)0x2107, AbStatus.TypeMismatch)]
|
||||
[InlineData((ushort)0x9999, AbStatus.BadReply)]
|
||||
public void General_status_FF_maps_by_extended_status(ushort ext, AbStatus expected) =>
|
||||
AbStatusCodes.FromCip(0xFF, [ext]).ShouldBe(expected);
|
||||
|
||||
[Theory]
|
||||
[InlineData((byte)0x00, null, AbStatus.Ok)]
|
||||
[InlineData((byte)0x10, null, AbStatus.Unsupported)]
|
||||
[InlineData((byte)0x20, null, AbStatus.NotFound)]
|
||||
[InlineData((byte)0x30, null, AbStatus.NotAllowed)]
|
||||
[InlineData((byte)0x40, null, AbStatus.DeviceStateConflict)]
|
||||
[InlineData((byte)0xF0, (byte)0x06, AbStatus.NotFound)]
|
||||
[InlineData((byte)0xF0, (byte)0x07, AbStatus.NotFound)]
|
||||
[InlineData((byte)0xF0, (byte)0x0A, AbStatus.BadReply)]
|
||||
public void Pccc_status_maps(byte sts, byte? ext, AbStatus expected) =>
|
||||
AbStatusCodes.FromPccc(sts, ext).ShouldBe(expected);
|
||||
|
||||
[Fact]
|
||||
public void Ok_is_zero_and_every_error_is_negative()
|
||||
{
|
||||
((int)AbStatus.Ok).ShouldBe(0);
|
||||
foreach (var s in Enum.GetValues<AbStatus>().Where(s => s is not AbStatus.Ok and not AbStatus.Pending))
|
||||
((int)s).ShouldBeLessThan(0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Cip;
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests.Wire;
|
||||
|
||||
public sealed class CipReplyTests
|
||||
{
|
||||
[Fact]
|
||||
public void Parses_success_reply_with_data()
|
||||
{
|
||||
// 0xCC = reply to Read Tag 0x4C; reserved; status 0; ext size 0; data C4 00 2A 00 00 00
|
||||
var r = CipReply.Parse(new byte[] { 0xCC, 0x00, 0x00, 0x00, 0xC4, 0x00, 0x2A, 0x00, 0x00, 0x00 });
|
||||
r.Service.ShouldBe((byte)0x4C);
|
||||
r.GeneralStatus.ShouldBe((byte)0);
|
||||
r.ExtendedStatus.Length.ShouldBe(0);
|
||||
r.Data.ToArray().ShouldBe(new byte[] { 0xC4, 0x00, 0x2A, 0x00, 0x00, 0x00 });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parses_reply_with_one_extended_word()
|
||||
{
|
||||
var r = CipReply.Parse(new byte[] { 0xCC, 0x00, 0xFF, 0x01, 0x07, 0x21 });
|
||||
r.ExtendedStatus.ToArray().ShouldBe(new ushort[] { 0x2107 });
|
||||
r.Status.ShouldBe(AbStatus.TypeMismatch);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parses_error_reply_with_two_extended_words()
|
||||
{
|
||||
var r = CipReply.Parse(new byte[] { 0xCC, 0x00, 0xFF, 0x02, 0x05, 0x21, 0x00, 0x00 });
|
||||
r.GeneralStatus.ShouldBe((byte)0xFF);
|
||||
r.ExtendedStatus.ToArray().ShouldBe(new ushort[] { 0x2105, 0x0000 });
|
||||
r.Data.Length.ShouldBe(0);
|
||||
r.Status.ShouldBe(AbStatus.OutOfBounds);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Truncated_reply_throws_BadReply()
|
||||
{
|
||||
var ex = Should.Throw<AbTransportException>(() => CipReply.Parse(new byte[] { 0xCC, 0x00 }));
|
||||
ex.Status.ShouldBe(AbStatus.BadReply);
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<RootNamespace>ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="xunit.v3"/>
|
||||
<PackageReference Include="Shouldly"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk"/>
|
||||
<PackageReference Include="xunit.runner.visualstudio">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Drivers\ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport\ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user