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)] [InlineData((byte)0x07, AbStatus.BadReply)] // connection lost: opaque on purpose (AbLegacy #738) // Lane K F1 (#737): the controller answered and refused this request — not a garbled reply. [InlineData((byte)0x1C, AbStatus.Unsupported)] // missing attribute list entry data [InlineData((byte)0x20, AbStatus.Unsupported)] // invalid parameter public void Cip_general_status_maps(byte general, AbStatus expected) => AbStatusCodes.FromCip(general, ReadOnlySpan.Empty).ShouldBe(expected); [Theory] [InlineData((ushort)0x2104, AbStatus.OutOfBounds)] // offset beyond end of the tag (Lane K F1) [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); [Fact] public void General_status_FF_with_no_extended_status_is_BadReply() => AbStatusCodes.FromCip(0xFF, ReadOnlySpan.Empty).ShouldBe(AbStatus.BadReply); [Theory] [InlineData((byte)0x00, null, AbStatus.Ok)] [InlineData((byte)0x10, null, AbStatus.Unsupported)] // illegal command or format // Lane K F3 (AbLegacy F8, review 07 r0 I4), per the DF1/PCCC STS table (1770-6.5.16): [InlineData((byte)0x20, null, AbStatus.BadConnection)] // host has a problem and will not communicate [InlineData((byte)0x30, null, AbStatus.BadConnection)] // remote node host missing / disconnected / shut down [InlineData((byte)0x40, null, AbStatus.DeviceStateConflict)] // host hardware fault (the host answered) [InlineData((byte)0x50, null, AbStatus.NotFound)] // addressing problem or memory protect rungs [InlineData((byte)0x60, null, AbStatus.NotAllowed)] // function not allowed due to command protection [InlineData((byte)0x70, null, AbStatus.DeviceStateConflict)] // processor is in Program mode [InlineData((byte)0x80, null, AbStatus.BadReply)] // compatibility-mode file missing: unclassified [InlineData((byte)0x90, null, AbStatus.BadConnection)] // remote node cannot buffer command [InlineData((byte)0xB0, null, AbStatus.DeviceStateConflict)] // remote node problem due to download [InlineData((byte)0xF0, (byte)0x06, AbStatus.NotFound)] // address does not point to something usable [InlineData((byte)0xF0, (byte)0x07, AbStatus.OutOfBounds)] // file is wrong size [InlineData((byte)0xF0, (byte)0x0A, AbStatus.BadReply)] [InlineData((byte)0xF0, null, AbStatus.BadReply)] public void Pccc_status_maps(byte sts, byte? ext, AbStatus expected) => AbStatusCodes.FromPccc(sts, ext).ShouldBe(expected); /// /// A dead DH+/DH-485 node behind a bridge answers STS 0x30 (and a busy one 0x90). Neither proves the END /// device answered, so they are the no-reply comms class, never one of the "answered" refusals — that /// classification made such a device count as reachable forever (no backoff, no comms-loss watchdog). /// /// The PCCC STS byte. [Theory] [InlineData((byte)0x20)] [InlineData((byte)0x30)] [InlineData((byte)0x90)] public void Pccc_no_reply_statuses_are_never_answered_refusals(byte sts) { var status = AbStatusCodes.FromPccc(sts, null); status.ShouldBe(AbStatus.BadConnection); status.ShouldNotBeOneOf(AbStatus.NotFound, AbStatus.NotAllowed, AbStatus.OutOfBounds, AbStatus.DeviceStateConflict, AbStatus.TooLarge, AbStatus.TooSmall, AbStatus.TypeMismatch); } [Fact] public void Ok_is_zero_and_every_error_is_negative() { ((int)AbStatus.Ok).ShouldBe(0); foreach (var s in Enum.GetValues().Where(s => s is not AbStatus.Ok and not AbStatus.Pending)) ((int)s).ShouldBeLessThan(0); } }