4e6eed7454
#570 - STRING: the built-in 88-byte Logix STRING holds 82 characters (LEN + DATA[82] + 2 pad), not 84; the write still sends the whole 88-byte structure and a read never decodes the pad as characters. - Mistype churn: a type refusal (CIP 0xFF/0x2107) on the existence read no longer throws from InitializeAsync, so the runtime is not disposed/re-created (and re-Forward-Opened) every poll; the driver also keeps a runtime that throws TypeMismatch from init and records the device as reachable. - Malformed tag name: a TagPath that does not parse, or a name the CIP symbolic encoder refuses (now also non-ASCII, empty member, >255-char member, unterminated subscript), is AbCipTagConfigurationException -> per-tag BadConfigurationError (named const in AbCipStatusMapper), warned once, never feeding the device backoff or health; siblings keep reading. A malformed probe tag no longer feeds the backoff either. - Micro800 BYTE/WORD/LWORD (0xD1/0xD2/0xD4) are type-checked as integers of their width: REAL/LREAL or a narrower integer over LWORD is TypeMismatch, not decoded garbage. #579 - ConnectionLease.ReleaseInBackground: one non-blocking release shape for both native AB runtimes. The AbCip runtime's Dispose no longer waits up to 2 s under the per-tag lock; the pool still registers the close synchronously so a re-create cannot overlap it. Offline soak tests (25 cycles, AbCip + AbLegacy) prove no leaked connection; FakeEipServer gains OpenConnections. Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
73 lines
3.1 KiB
C#
73 lines
3.1 KiB
C#
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Cip;
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests.Cip;
|
|
|
|
public sealed class CipPathTests
|
|
{
|
|
[Theory]
|
|
[InlineData("1,0", "0100")]
|
|
[InlineData("", "")]
|
|
[InlineData("1,0,2,10.0.0.5", "0100" + "1208" + "31302E302E302E35")]
|
|
[InlineData("2,10.0.0.55", "1209" + "31302E302E302E3535" + "00")]
|
|
[InlineData("15,3", "0F0F0003")]
|
|
[InlineData("16,10.0.0.5", "1F08" + "1000" + "31302E302E302E35")]
|
|
public void Encodes_route(string path, string hex) =>
|
|
Convert.ToHexString(CipPath.EncodeRoute(path)).ShouldBe(hex);
|
|
|
|
[Fact]
|
|
public void Odd_route_parts_throw() =>
|
|
Should.Throw<ArgumentException>(() => CipPath.EncodeRoute("1,0,2"));
|
|
|
|
[Theory]
|
|
[InlineData("TestDINT", "9108546573744449 4E54")]
|
|
[InlineData("Abc", "910341626300")]
|
|
[InlineData("Program:Main.Arr[3].Val", "910C50726F6772616D3A4D61696E" + "910341727200" + "2803" + "910356616C00")]
|
|
[InlineData("Big[300]", "910342696700" + "29002C01")]
|
|
[InlineData("Huge[70000]", "910448756765" + "2A00" + "70110100")]
|
|
[InlineData("Grid[1,2]", "910447726964" + "2801" + "2802")]
|
|
public void Encodes_symbolic(string name, string hex) =>
|
|
Convert.ToHexString(CipPath.EncodeSymbolic(name)).ShouldBe(hex.Replace(" ", ""));
|
|
|
|
[Fact]
|
|
public void Symbolic_rejects_bit_selector() =>
|
|
Should.Throw<ArgumentException>(() => CipPath.EncodeSymbolic("Tag.5"));
|
|
|
|
/// <summary>Gitea #570: names that used to be encoded silently wrong (a non-ASCII name as '?', an over-long
|
|
/// one with a wrapped length byte, an empty member as a zero-length segment) or that threw an unrelated
|
|
/// exception (an unterminated subscript) are refused as invalid arguments before any I/O.</summary>
|
|
[Theory]
|
|
[InlineData("Tëmp")]
|
|
[InlineData("A..B")]
|
|
[InlineData("Arr[1")]
|
|
public void Symbolic_rejects_names_it_cannot_encode_faithfully(string name) =>
|
|
Should.Throw<ArgumentException>(() => CipPath.EncodeSymbolic(name));
|
|
|
|
[Fact]
|
|
public void Symbolic_rejects_a_member_longer_than_its_length_byte()
|
|
{
|
|
Should.Throw<ArgumentException>(() => CipPath.EncodeSymbolic(new string('A', 256)));
|
|
CipPath.EncodeSymbolic(new string('A', 255))[1].ShouldBe((byte)255); // the boundary still encodes
|
|
}
|
|
|
|
[Fact]
|
|
public void Splits_bit_selector()
|
|
{
|
|
CipPath.SplitBitSelector("Tag.5").ShouldBe(("Tag", 5));
|
|
CipPath.SplitBitSelector("Tag.Member").ShouldBe(("Tag.Member", null));
|
|
CipPath.SplitBitSelector("Tag").ShouldBe(("Tag", null));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(2, 1u, "20022401")]
|
|
[InlineData(0x6B, 0x1234u, "206B25003412")]
|
|
[InlineData(0x300, 1u, "21000003" + "2401")]
|
|
[InlineData(2, 70000u, "2002" + "2600" + "70110100")]
|
|
public void Encodes_class_instance(ushort cls, uint inst, string hex) =>
|
|
Convert.ToHexString(CipPath.EncodeClassInstance(cls, inst)).ShouldBe(hex);
|
|
|
|
[Fact]
|
|
public void Prefixes_path_size_in_words() =>
|
|
Convert.ToHexString(CipPath.WithPathSize([0x20, 0x02, 0x24, 0x01])).ShouldBe("0220022401");
|
|
}
|