using Shouldly; using Xunit; namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus.Tests; /// /// R07:L5 (Modbus half): the RTU ADU is now written into a pooled buffer and the response is deframed in one pooled /// buffer. The wire bytes and the decoded PDUs must be exactly what the allocating code produced, and a reused pooled /// buffer must never leak a previous frame's bytes into the next result. /// [Trait("Category", "Unit")] public sealed class ModbusPooledFramingTests { /// The pooled ADU writer matches the reference construction (unit + PDU, CRC appended low byte first). /// The PDU as hex. [Theory] [InlineData("0300000001")] [InlineData("10000A0002040001000200")] [InlineData("06")] public void WriteAdu_matches_the_reference_ADU(string hex) { var pdu = Convert.FromHexString(hex); var reference = ModbusCrc.Append([0x11, .. pdu]); var pooled = new byte[ModbusRtuFraming.AduLength(pdu.Length) + 7]; // oversized, like a rented array ModbusRtuFraming.WriteAdu(0x11, pdu, pooled.AsSpan(0, ModbusRtuFraming.AduLength(pdu.Length))); pooled.AsSpan(0, reference.Length).ToArray().ShouldBe(reference); ModbusRtuFraming.BuildAdu(0x11, pdu).ShouldBe(reference); } /// /// A long read reply followed by a short echo and an exception frame, each deframed through the pooled buffer: /// every result is exactly its own PDU (no stale tail from the previous, longer frame). /// [Fact] public async Task Consecutive_deframes_through_the_pooled_buffer_do_not_leak_bytes() { var ct = TestContext.Current.CancellationToken; byte[] longRead = [0x03, 200, .. Enumerable.Range(0, 200).Select(i => (byte)(i + 1))]; byte[] echo = [0x06, 0x00, 0x01, 0x00, 0x2A]; (await ModbusRtuFraming.ReadResponsePduAsync(new MemoryStream(ModbusRtuFraming.BuildAdu(1, longRead)), 1, ct)) .ShouldBe(longRead); (await ModbusRtuFraming.ReadResponsePduAsync(new MemoryStream(ModbusRtuFraming.BuildAdu(1, echo)), 1, ct)) .ShouldBe(echo); var ex = await Should.ThrowAsync(() => ModbusRtuFraming.ReadResponsePduAsync(new MemoryStream(ModbusRtuFraming.BuildAdu(1, [0x83, 0x02])), 1, ct)); ex.ExceptionCode.ShouldBe((byte)0x02); (await ModbusRtuFraming.ReadResponsePduAsync(new MemoryStream(ModbusRtuFraming.BuildAdu(1, [0x03, 2, 9, 8])), 1, ct)) .ShouldBe(new byte[] { 0x03, 2, 9, 8 }); } /// A byte-count-255 read (the largest RTU reply) fits the pooled frame. [Fact] public async Task The_largest_read_reply_deframes() { byte[] max = [0x04, 255, .. new byte[255]]; (await ModbusRtuFraming.ReadResponsePduAsync(new MemoryStream(ModbusRtuFraming.BuildAdu(1, max)), 1, TestContext.Current.CancellationToken)).ShouldBe(max); } }