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));
}
}
}