public async Task ReadsPipeAgainIfNotEnoughtData()
        {
            // Arrange
            var request          = new ReadHoldingRegistersRequest(1, 1, 1);
            var response         = new byte[] { 0, 1, 0, 0, 0, 5, 1, 3, 2, 0, 0 };
            var responseSequence = new ReadOnlySequence <byte>(response.AsMemory());
            var pipeAdapterMock  = new Mock <IPipeResource>();

            pipeAdapterMock.Setup(x => x.WriteAsync(It.IsAny <ReadOnlyMemory <byte> >(), It.IsAny <CancellationToken>())).Returns(Task.CompletedTask);
            pipeAdapterMock.Setup(x => x.ReadAsync(6, It.IsAny <CancellationToken>())).ReturnsAsync(responseSequence.Slice(0, 6));
            pipeAdapterMock.Setup(x => x.ReadAsync(11, It.IsAny <CancellationToken>())).ReturnsAsync(responseSequence);
            var transactionIdProviderMock = new Mock <ITransactionIdProvider>();

            transactionIdProviderMock.Setup(x => x.NewId()).Returns(1);

            var target = new ModbusTcpTransport(pipeAdapterMock.Object, transactionIdProviderMock.Object, Mock.Of <ILogger <IModbusMaster> >());

            // Act
            var actual = await target.SendAsync <ReadHoldingRegistersResponse>(request, It.IsAny <CancellationToken>());

            // Assert
            pipeAdapterMock.Verify(x => x.WriteAsync(It.IsAny <ReadOnlyMemory <byte> >(), It.IsAny <CancellationToken>()), Times.Once());
            pipeAdapterMock.Verify(x => x.ReadAsync(6, It.IsAny <CancellationToken>()), Times.Once());
            pipeAdapterMock.Verify(x => x.ReadAsync(6, It.IsAny <CancellationToken>()), Times.Once());
            pipeAdapterMock.Verify(x => x.MarkExamined(responseSequence.Slice(0, 6)), Times.Once());
            pipeAdapterMock.Verify(x => x.MarkConsumed(responseSequence), Times.Once());
        }
        public async Task RetriesOnOldTransactionUsingRetryCount()
        {
            // Arrange
            var request         = new ReadHoldingRegistersRequest(1, 1, 1);
            var oldResponse     = new ReadOnlySequence <byte>(new byte[] { 0, 1, 0, 0, 0, 5, 1, 3, 2, 0, 0 }.AsMemory());
            var newResponse     = new ReadOnlySequence <byte>(new byte[] { 0, 2, 0, 0, 0, 5, 1, 3, 2, 0, 0 }.AsMemory());
            var pipeAdapterMock = new Mock <IPipeResource>();

            pipeAdapterMock.Setup(x => x.WriteAsync(It.IsAny <ReadOnlyMemory <byte> >(), It.IsAny <CancellationToken>())).Returns(Task.CompletedTask);
            pipeAdapterMock.SetupSequence(x => x.ReadAsync(It.IsAny <int>(), It.IsAny <CancellationToken>()))
            .ReturnsAsync(oldResponse)
            .ReturnsAsync(oldResponse)
            .ReturnsAsync(newResponse);
            var transactionIdProviderMock = new Mock <ITransactionIdProvider>();

            transactionIdProviderMock.Setup(x => x.NewId()).Returns(2);
            var target = new ModbusTcpTransport(pipeAdapterMock.Object, transactionIdProviderMock.Object, Mock.Of <ILogger <IModbusMaster> >())
            {
                Retries = 2
            };

            // Act
            var actual = await target.SendAsync <ReadHoldingRegistersResponse>(request);

            // Assert
            pipeAdapterMock.Verify(x => x.WriteAsync(It.IsAny <ReadOnlyMemory <byte> >(), It.IsAny <CancellationToken>()), Times.Exactly(3));
            pipeAdapterMock.Verify(x => x.ReadAsync(It.IsAny <int>(), It.IsAny <CancellationToken>()), Times.Exactly(3));
        }
        public async Task ThrowsOnOldTransactionIfDifferenceIsEqualToThreshold()
        {
            // Arrange
            var request         = new ReadHoldingRegistersRequest(1, 1, 1);
            var oldResponse     = new ReadOnlySequence <byte>(new byte[] { 0, 3, 0, 0, 0, 5, 1, 3, 2, 0, 0 }.AsMemory());
            var newResponse     = new ReadOnlySequence <byte>(new byte[] { 0, 5, 0, 0, 0, 5, 1, 3, 2, 0, 0 }.AsMemory());
            var pipeAdapterMock = new Mock <IPipeResource>();

            pipeAdapterMock.Setup(x => x.WriteAsync(It.IsAny <ReadOnlyMemory <byte> >(), It.IsAny <CancellationToken>())).Returns(Task.CompletedTask);
            pipeAdapterMock.SetupSequence(x => x.ReadAsync(It.IsAny <int>(), It.IsAny <CancellationToken>()))
            .ReturnsAsync(oldResponse)
            .ReturnsAsync(newResponse);
            var transactionIdProviderMock = new Mock <ITransactionIdProvider>();

            transactionIdProviderMock.Setup(x => x.NewId()).Returns(2);
            var target = new ModbusTcpTransport(pipeAdapterMock.Object, transactionIdProviderMock.Object, Mock.Of <ILogger <IModbusMaster> >())
            {
                Retries = 0,
                RetryOnOldResponseThreshold = 2
            };

            // Act
            await Assert.ThrowsAsync <IOException>(() => target.SendAsync <ReadHoldingRegistersResponse>(request));

            // Assert
            pipeAdapterMock.Verify(x => x.WriteAsync(It.IsAny <ReadOnlyMemory <byte> >(), It.IsAny <CancellationToken>()), Times.Once());
            pipeAdapterMock.Verify(x => x.ReadAsync(It.IsAny <int>(), It.IsAny <CancellationToken>()), Times.Once());
        }