7892eb13a8
CIP codec (#564): PatchU16 bounds guard; CipReply content equality; CipPath port/length/ASCII/odd/oversize validation with ArgumentException for malformed tag names and routes; CipPath.MessageRouter/ConnectionManager are now ReadOnlySpan<byte>; refused Forward Open surfaces ForwardOpenRefusedException with structured general/extended status; test CaptureFile uses BinaryPrimitives. EncapsulationClient (#565): frame write bounded by the exchange timeout, which faults (never cancels) the connection; finite positive timeouts enforced at every entry point; gate wait bounded by the timeout; deterministic reorder of the 300 ms test; new dispose-during-drain, gate-wait and RegisterSession zero-context tests. Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
63 lines
2.3 KiB
C#
63 lines
2.3 KiB
C#
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 Replies_parsed_from_the_same_bytes_are_equal()
|
|
{
|
|
byte[] raw = [0xCC, 0x00, 0xFF, 0x02, 0x05, 0x21, 0x00, 0x00, 0xAA];
|
|
// Distinct arrays on both sides: reference equality (the record default) would say unequal.
|
|
var a = CipReply.Parse(raw.ToArray());
|
|
var b = CipReply.Parse(raw.ToArray());
|
|
a.ShouldBe(b);
|
|
a.GetHashCode().ShouldBe(b.GetHashCode());
|
|
}
|
|
|
|
[Fact]
|
|
public void Replies_differing_in_extended_status_or_data_are_not_equal()
|
|
{
|
|
var a = CipReply.Parse(new byte[] { 0xCC, 0x00, 0xFF, 0x01, 0x07, 0x21, 0x01 });
|
|
a.ShouldNotBe(CipReply.Parse(new byte[] { 0xCC, 0x00, 0xFF, 0x01, 0x05, 0x21, 0x01 }));
|
|
a.ShouldNotBe(CipReply.Parse(new byte[] { 0xCC, 0x00, 0xFF, 0x01, 0x07, 0x21, 0x02 }));
|
|
}
|
|
|
|
[Fact]
|
|
public void Truncated_reply_throws_BadReply()
|
|
{
|
|
var ex = Should.Throw<AbTransportException>(() => CipReply.Parse(new byte[] { 0xCC, 0x00 }));
|
|
ex.Status.ShouldBe(AbStatus.BadReply);
|
|
}
|
|
}
|