using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Net; using log4net; using Modbus.Message; using Modbus.Utility; namespace Modbus.IO { /// /// Transport for Internet protocols. /// Refined Abstraction - http://en.wikipedia.org/wiki/Bridge_Pattern /// internal class ModbusIpTransport : ModbusTransport { private static readonly ILog _logger = LogManager.GetLogger(typeof(ModbusIpTransport)); private static readonly object _transactionIdLock = new object(); private ushort _transactionId; internal ModbusIpTransport(IStreamResource streamResource) : base(streamResource) { Debug.Assert(streamResource != null, "Argument streamResource cannot be null."); } internal static byte[] ReadRequestResponse(IStreamResource streamResource) { // read header byte[] mbapHeader = new byte[6]; int numBytesRead = 0; while (numBytesRead != 6) { numBytesRead += streamResource.Read(mbapHeader, numBytesRead, 6 - numBytesRead); if (numBytesRead == 0) throw new IOException("Read resulted in 0 bytes returned."); } _logger.DebugFormat("MBAP header: {0}", StringUtility.Join(", ", mbapHeader)); ushort frameLength = (ushort) (IPAddress.HostToNetworkOrder(BitConverter.ToInt16(mbapHeader, 4))); _logger.DebugFormat("{0} bytes in PDU.", frameLength); // read message byte[] messageFrame = new byte[frameLength]; numBytesRead = 0; while (numBytesRead != frameLength) { numBytesRead += streamResource.Read(messageFrame, numBytesRead, frameLength - numBytesRead); if (numBytesRead == 0) throw new IOException("Read resulted in 0 bytes returned."); } _logger.DebugFormat("PDU: {0}", frameLength); byte[] frame = CollectionUtility.Concat(mbapHeader, messageFrame); _logger.InfoFormat("RX: {0}", StringUtility.Join(", ", frame)); return frame; } internal static byte[] GetMbapHeader(IModbusMessage message) { byte[] transactionId = BitConverter.GetBytes((short) IPAddress.HostToNetworkOrder((short) (message.TransactionId))); byte[] protocol = { 0, 0 }; byte[] length = BitConverter.GetBytes((short) IPAddress.HostToNetworkOrder((short) (message.ProtocolDataUnit.Length + 1))); return CollectionUtility.Concat(transactionId, protocol, length, new byte[] { message.SlaveAddress }); } /// /// Create a new transaction ID. /// internal virtual ushort GetNewTransactionId() { lock (_transactionIdLock) _transactionId = _transactionId == UInt16.MaxValue ? (ushort) 1 : ++_transactionId; return _transactionId; } internal IModbusMessage CreateMessageAndInitializeTransactionId(byte[] fullFrame) where T : IModbusMessage, new() { byte[] mbapHeader = CollectionUtility.Slice(fullFrame, 0, 6); byte[] messageFrame = CollectionUtility.Slice(fullFrame, 6, fullFrame.Length - 6); IModbusMessage response = base.CreateResponse(messageFrame); response.TransactionId = (ushort) IPAddress.NetworkToHostOrder(BitConverter.ToInt16(mbapHeader, 0)); return response; } internal override byte[] BuildMessageFrame(IModbusMessage message) { List messageBody = new List(); messageBody.AddRange(GetMbapHeader(message)); messageBody.AddRange(message.ProtocolDataUnit); return messageBody.ToArray(); } internal override void Write(IModbusMessage message) { message.TransactionId = GetNewTransactionId(); byte[] frame = BuildMessageFrame(message); _logger.InfoFormat("TX: {0}", StringUtility.Join(", ", frame)); StreamResource.Write(frame, 0, frame.Length); } internal override byte[] ReadRequest() { return ReadRequestResponse(StreamResource); } internal override IModbusMessage ReadResponse() { return CreateMessageAndInitializeTransactionId(ReadRequestResponse(StreamResource)); } internal override void OnValidateResponse(IModbusMessage request, IModbusMessage response) { if (request.TransactionId != response.TransactionId) throw new IOException(String.Format(CultureInfo.InvariantCulture, "Response was not of expected transaction ID. Expected {0}, received {1}.", request.TransactionId, response.TransactionId)); } } }