using System; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using NModbus.Logging; using NModbus.Unme.Common; namespace NModbus.IO { /// /// Transport for Internet protocols. /// Refined Abstraction - http://en.wikipedia.org/wiki/Bridge_Pattern /// public class ModbusIpTransport : ModbusTransport { private static readonly object _transactionIdLock = new object(); private ushort _transactionId; public ModbusIpTransport(IStreamResource streamResource, IModbusFactory modbusFactory, IModbusLogger logger) : base(streamResource, modbusFactory, logger) { if (streamResource == null) throw new ArgumentNullException(nameof(streamResource)); } public static byte[] ReadRequestResponse(IStreamResource streamResource, IModbusLogger logger) { if (streamResource == null) throw new ArgumentNullException(nameof(streamResource)); if (logger == null) throw new ArgumentNullException(nameof(logger)); // read header var mbapHeader = new byte[6]; int numBytesRead = 0; while (numBytesRead != 6) { int bRead = streamResource.Read(mbapHeader, numBytesRead, 6 - numBytesRead); if (bRead == 0) { throw new IOException("Read resulted in 0 bytes returned."); } numBytesRead += bRead; } logger.Debug($"MBAP header: {string.Join(", ", mbapHeader)}"); var frameLength = (ushort)IPAddress.HostToNetworkOrder(BitConverter.ToInt16(mbapHeader, 4)); logger.Debug($"{frameLength} bytes in PDU."); // read message var messageFrame = new byte[frameLength]; numBytesRead = 0; while (numBytesRead != frameLength) { int bRead = streamResource.Read(messageFrame, numBytesRead, frameLength - numBytesRead); if (bRead == 0) { throw new IOException("Read resulted in 0 bytes returned."); } numBytesRead += bRead; } logger.Debug($"PDU: {frameLength}"); var frame = mbapHeader.Concat(messageFrame).ToArray(); logger.LogFrameRx(frame); return frame; } public static byte[] GetMbapHeader(IModbusMessage message) { byte[] transactionId = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)message.TransactionId)); byte[] length = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)(message.ProtocolDataUnit.Length + 1))); var stream = new MemoryStream(7); stream.Write(transactionId, 0, transactionId.Length); stream.WriteByte(0); stream.WriteByte(0); stream.Write(length, 0, length.Length); stream.WriteByte(message.SlaveAddress); return stream.ToArray(); } /// /// Create a new transaction ID. /// public virtual ushort GetNewTransactionId() { lock (_transactionIdLock) { _transactionId = _transactionId == ushort.MaxValue ? (ushort)1 : ++_transactionId; } return _transactionId; } public IModbusMessage CreateMessageAndInitializeTransactionId(byte[] fullFrame) where T : IModbusMessage, new() { byte[] mbapHeader = fullFrame.Slice(0, 6).ToArray(); byte[] messageFrame = fullFrame.Slice(6, fullFrame.Length - 6).ToArray(); IModbusMessage response = CreateResponse(messageFrame); response.TransactionId = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(mbapHeader, 0)); return response; } public override byte[] BuildMessageFrame(IModbusMessage message) { byte[] header = GetMbapHeader(message); byte[] pdu = message.ProtocolDataUnit; var messageBody = new MemoryStream(header.Length + pdu.Length); messageBody.Write(header, 0, header.Length); messageBody.Write(pdu, 0, pdu.Length); return messageBody.ToArray(); } public override void Write(IModbusMessage message) { message.TransactionId = GetNewTransactionId(); byte[] frame = BuildMessageFrame(message); Logger.LogFrameTx(frame); StreamResource.Write(frame, 0, frame.Length); } public override byte[] ReadRequest() { return ReadRequestResponse(StreamResource, Logger); } public override IModbusMessage ReadResponse() { return CreateMessageAndInitializeTransactionId(ReadRequestResponse(StreamResource, Logger)); } internal override void OnValidateResponse(IModbusMessage request, IModbusMessage response) { if (request.TransactionId != response.TransactionId) { string msg = $"Response was not of expected transaction ID. Expected {request.TransactionId}, received {response.TransactionId}."; throw new IOException(msg); } } public override bool OnShouldRetryResponse(IModbusMessage request, IModbusMessage response) { if (request.TransactionId > response.TransactionId && request.TransactionId - response.TransactionId < RetryOnOldResponseThreshold) { // This response was from a previous request return true; } return base.OnShouldRetryResponse(request, response); } } }