using Shouldly; using Xunit; namespace ZB.MOM.WW.OtOpcUa.Driver.S7.Tests; /// /// Unit tests for the S7 wide-type (8-byte numeric) byte-buffer codec: the pure /// / /// helpers and . These decode/encode an /// Int64/UInt64/LReal (Float64) scalar from a contiguous big-endian byte block — the /// network I/O half (Plc.ReadBytesAsync/WriteBytesAsync) has no in-process /// fake so only the codec is unit-proven (mirrors ). /// String/DateTime decode is a deferred stub here (T3/T4); this file pins the /// NotSupportedException contract those land against. /// [Trait("Category", "Unit")] public sealed class S7ScalarBlockTests { // ── Helpers ────────────────────────────────────────────────────────────────────────── // Wide scalars are byte-anchored: DB{n}.DBB{offset}, parser yields S7Size.Byte. private static S7TagDefinition Tag(S7DataType dt, int stringLength = 254) => new("WideTag", "DB1.DBB0", dt, StringLength: stringLength); private static S7ParsedAddress Addr() => new(S7Area.DataBlock, DbNumber: 1, S7Size.Byte, ByteOffset: 0, BitOffset: 0); // S7 is big-endian: most-significant byte first. private static byte[] BeUInt64(ulong v) { var b = new byte[8]; for (var i = 0; i < 8; i++) b[i] = (byte)(v >> (56 - i * 8)); return b; } // ── ScalarByteWidth ─────────────────────────────────────────────────────────────────── /// Verifies the 8-byte numeric widths and the String/DateTime widths. [Theory] [InlineData(S7DataType.Int64, 8)] [InlineData(S7DataType.UInt64, 8)] [InlineData(S7DataType.Float64, 8)] [InlineData(S7DataType.DateTime, 8)] public void ScalarByteWidth_fixed_width_types(S7DataType dt, int expected) => S7Driver.ScalarByteWidth(Tag(dt)).ShouldBe(expected); /// Verifies String width is StringLength + 2 (S7 STRING header: max-len + actual-len). [Fact] public void ScalarByteWidth_String_is_length_plus_two() => S7Driver.ScalarByteWidth(Tag(S7DataType.String, stringLength: 10)).ShouldBe(12); // ── DecodeScalarBlock — Int64 ───────────────────────────────────────────────────────── /// Verifies an Int64 block decodes from big-endian bytes. [Fact] public void DecodeScalarBlock_Int64_reads_big_endian() { var block = BeUInt64(0x0123456789ABCDEFUL); // First byte is the most-significant byte (0x01) — proves big-endian, not little-endian. block[0].ShouldBe((byte)0x01); var result = S7Driver.DecodeScalarBlock(Tag(S7DataType.Int64), Addr(), block); result.ShouldBeOfType().ShouldBe(0x0123456789ABCDEFL); } /// Verifies a negative Int64 decodes correctly (two's complement, high bit set). [Fact] public void DecodeScalarBlock_Int64_negative() { var block = BeUInt64(unchecked((ulong)-2L)); // 0xFFFF_FFFF_FFFF_FFFE var result = S7Driver.DecodeScalarBlock(Tag(S7DataType.Int64), Addr(), block); result.ShouldBeOfType().ShouldBe(-2L); } // ── DecodeScalarBlock — UInt64 ──────────────────────────────────────────────────────── /// Verifies a UInt64 block decodes a value larger than long.MaxValue. [Fact] public void DecodeScalarBlock_UInt64_reads_value_above_long_max() { var block = BeUInt64(ulong.MaxValue); // 0xFFFF_FFFF_FFFF_FFFF var result = S7Driver.DecodeScalarBlock(Tag(S7DataType.UInt64), Addr(), block); result.ShouldBeOfType().ShouldBe(ulong.MaxValue); } // ── DecodeScalarBlock — Float64 (LReal) ─────────────────────────────────────────────── /// Verifies a Float64 (LReal) block decodes from IEEE-754 big-endian. [Fact] public void DecodeScalarBlock_Float64_reads_ieee754_big_endian() { var bits = unchecked((ulong)BitConverter.DoubleToInt64Bits(Math.PI)); var block = BeUInt64(bits); var result = S7Driver.DecodeScalarBlock(Tag(S7DataType.Float64), Addr(), block); result.ShouldBeOfType().ShouldBe(Math.PI, tolerance: 1e-12); } // ── EncodeScalarBlock — big-endian byte production ──────────────────────────────────── /// Verifies Int64 encodes to big-endian bytes (MSB first). [Fact] public void EncodeScalarBlock_Int64_writes_big_endian() { var bytes = S7Driver.EncodeScalarBlock(Tag(S7DataType.Int64), 0x0123456789ABCDEFL); bytes.Length.ShouldBe(8); bytes.ShouldBe(BeUInt64(0x0123456789ABCDEFUL)); bytes[0].ShouldBe((byte)0x01); // MSB first — little-endian regression guard. } /// Verifies UInt64 encodes to big-endian bytes. [Fact] public void EncodeScalarBlock_UInt64_writes_big_endian() { var bytes = S7Driver.EncodeScalarBlock(Tag(S7DataType.UInt64), ulong.MaxValue); bytes.ShouldBe(BeUInt64(ulong.MaxValue)); } /// Verifies Float64 encodes to IEEE-754 big-endian bytes. [Fact] public void EncodeScalarBlock_Float64_writes_ieee754_big_endian() { var bytes = S7Driver.EncodeScalarBlock(Tag(S7DataType.Float64), Math.PI); bytes.ShouldBe(BeUInt64(unchecked((ulong)BitConverter.DoubleToInt64Bits(Math.PI)))); } // ── Round-trip identity (encode → decode) ───────────────────────────────────────────── /// Verifies Int64 round-trips through encode→decode for positive, negative and edge values. [Theory] [InlineData(0L)] [InlineData(1L)] [InlineData(-1L)] [InlineData(long.MaxValue)] [InlineData(long.MinValue)] [InlineData(0x0123456789ABCDEFL)] public void Int64_round_trips(long value) { var tag = Tag(S7DataType.Int64); var decoded = S7Driver.DecodeScalarBlock(tag, Addr(), S7Driver.EncodeScalarBlock(tag, value)); decoded.ShouldBeOfType().ShouldBe(value); } /// Verifies UInt64 round-trips, including a large value above long.MaxValue. [Theory] [InlineData(0UL)] [InlineData(70_000UL)] [InlineData(ulong.MaxValue)] [InlineData(0x8000_0000_0000_0001UL)] public void UInt64_round_trips(ulong value) { var tag = Tag(S7DataType.UInt64); var decoded = S7Driver.DecodeScalarBlock(tag, Addr(), S7Driver.EncodeScalarBlock(tag, value)); decoded.ShouldBeOfType().ShouldBe(value); } /// Verifies Float64 (LReal) round-trips for representative doubles. [Theory] [InlineData(0.0)] [InlineData(3.141592653589793)] [InlineData(-2.5e-300)] [InlineData(1.7976931348623157e308)] public void Float64_round_trips(double value) { var tag = Tag(S7DataType.Float64); var decoded = S7Driver.DecodeScalarBlock(tag, Addr(), S7Driver.EncodeScalarBlock(tag, value)); decoded.ShouldBeOfType().ShouldBe(value); } // ── Deferred-stub contract: String/DateTime throw NotSupportedException ──────────────── /// Verifies String/DateTime decode is a deferred stub (NotSupportedException — T3/T4). /// The not-yet-implemented wide type. [Theory] [InlineData(S7DataType.String)] [InlineData(S7DataType.DateTime)] public void DecodeScalarBlock_String_or_DateTime_throws_NotSupported(S7DataType dt) { var tag = Tag(dt, stringLength: 10); var block = new byte[S7Driver.ScalarByteWidth(tag)]; Should.Throw(() => S7Driver.DecodeScalarBlock(tag, Addr(), block)); } /// Verifies String/DateTime encode is a deferred stub (NotSupportedException — T3/T4). /// The not-yet-implemented wide type. [Theory] [InlineData(S7DataType.String)] [InlineData(S7DataType.DateTime)] public void EncodeScalarBlock_String_or_DateTime_throws_NotSupported(S7DataType dt) => Should.Throw(() => S7Driver.EncodeScalarBlock(Tag(dt), "x")); }