//..............................................................................
//
// Packet definitions for Modbus
//

namespace io {

alignment (1);

//..............................................................................

// Modbus protocol variations

enum ModbusProtocol: uint8_t
{
	Rtu,
	Ascii,
	Tcp,
	RtuIp,
}

// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

// Modbus node roles

enum ModbusRole: uint8_t
{
	Master,
	Slave,
}

//..............................................................................

// function codes

enum ModbusFunc: uint8_t
{
	ReadCoils                  = 1,
	ReadDiscreteInputs         = 2,
	ReadHoldingRegisters       = 3,
	ReadInputRegisters         = 4,
	WriteCoil                  = 5,
	WriteRegister              = 6,
	ReadExceptionStatus	       = 7,
	Diagnostics                = 8,
	GetCommEventCounter        = 11,
	GetCommEventLog            = 12,
	WriteMultipleCoils         = 15,
	WriteMultipleRegisters     = 16,
	ReportServerId             = 17,
	ReadFileRecord             = 20,
	WriteFileRecord            = 21,
	MaskWriteRegister	       = 22,
	ReadWriteMultipleRegisters = 23,
	ReadFifoQueue              = 24,
	EncapsulatedInterface      = 43,

	ExceptionFlag              = 0x80,
}

// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

char const* getModbusFuncString (ModbusFunc func)
{
	static char const* stringTable [] =
	{
		"Unknown",                       // 0
		"Read Coils",                    // ReadCoils                  = 1,
		"Read Discrete Inputs",          // ReadDiscreteInputs         = 2,
		"Read Holding Registers",        // ReadHoldingRegisters       = 3,
		"Read Input Registers",          // ReadInputRegisters         = 4,
		"Write Coil",                    // WriteCoil                  = 5,
		"Write Register",                // WriteRegister              = 6,
		"Read Exception Status",         // ReadExceptionStatus        = 7,
		"Diagnostics",                   // Diagnostics                = 8,
		"Unknown",                       // 9
		"Unknown",                       // 10
		"Get Comm Event Counter",        // GetComEventCounter         = 11,
		"Get Comm Event Log",            // GetComEventLog             = 12,
		"Unknown",                       // 13
		"Unknown",                       // 14
		"Write Multiple Coils",          // WriteMultipleCoils         = 15,
		"Write Multiple Registers",      // WriteMultipleRegisters     = 16,
		"Report Server Id",              // ReportServerId             = 17,
		"Unknown",                       // 18
		"Unknown",                       // 19
		"Read File Record",              // ReadFileRecord             = 20,
		"Write File Record",             // WriteFileRecord            = 21,
		"Mask Write Register",           // MaskWriteRegister          = 22,
		"Read Write Multiple Registers", // ReadWriteMultipleRegisters = 23,
		"Read Fifo Queue",               // ReadFifoQueue              = 24,
	}

	return
		func < countof (stringTable) ?
			stringTable [func] :
		func == ModbusFunc.EncapsulatedInterface ?
			"Encapsulated Interface" :
		(func & ModbusFunc.ExceptionFlag) ?
			"Exception" :
			"Unknown";
}

//..............................................................................

// exception codes

enum ModbusException: uint8_t
{
	IllegalFunction         = 1,
	IllegalDataAddress      = 2,
	IllegalDataValue        = 3,
	ServerDeviceFailure     = 4,
	Acknowledge             = 5,
	ServerDeviceBusy        = 6,
	MemoryParityError       = 8,
	GatewayPathUnavailable  = 10,
	GatewayDeviceNoResponse = 11,
}

// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

char const* getModbusExceptionString (ModbusException exception)
{
	static char const* stringTable [] =
	{
		"Unknown",                    // 0
		"Illegal Function",           // IllegalFunction               = 1,
		"Illegal Data Address",       // IllegalDataAddress            = 2,
		"Illegal Data Value",         // IllegalDataValue              = 3,
		"Server Device Failure",      // ServerDeviceFailure           = 4,
		"Acknowledge",                // Acknowledge                   = 5,
		"Server Device Busy",         // ServerDeviceBusy              = 6,
		"Unknown",                    // 7
		"Memory Parity Error",        // MemoryParityError             = 8,
		"Unknown",                    // 9
		"Gateway Path Unavailable",   // GatewayPathUnavailable        = 10,
		"Gateway Device No Response", // GatewayTargetDeviceNoResponse = 11,
	}

	return exception < countof (stringTable) ? stringTable [exception] : "Unknown";
}

//..............................................................................

// particular function code PDUs

struct ModbusPduHdr
{
	io.ModbusFunc m_func;
}

// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

struct ModbusExceptionPdu: ModbusPduHdr
{
	ModbusException m_exception;
}

// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

struct ModbusReadPdu: ModbusPduHdr
{
	bigendian uint16_t m_address;
	bigendian uint16_t m_count;
}

// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

struct ModbusReadReplyPdu: ModbusPduHdr
{
	uint8_t m_size;

	// followed by read data
}

// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

struct ModbusWritePdu: ModbusPduHdr
{
	bigendian uint16_t m_address;
	bigendian uint16_t m_value;
}

// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

struct ModbusWriteMultiplePdu: ModbusPduHdr
{
	bigendian uint16_t m_address;
	bigendian uint16_t m_count;
	uint8_t m_size;

	// followed by write data
}

// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

struct ModbusWriteMultipleReplyPdu: ModbusPduHdr
{
	bigendian uint16_t m_address;
	bigendian uint16_t m_count;
}

// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

struct ModbusMaskWritePdu: ModbusPduHdr
{
	bigendian uint16_t m_address;
	bigendian uint16_t m_andMask;
	bigendian uint16_t m_orMask;
}

// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

enum
{
	ModbusMaxPduSize = sizeof (ModbusWriteMultiplePdu) + 255,
}

//..............................................................................

// calculates the actual size of PDU (-1 on error)

size_t errorcode getModbusPduSize (
	ModbusRole role,
	ModbusPduHdr const* pduHdr,
	size_t size
	)
{
	if (size < sizeof (ModbusPduHdr))
		return sizeof (ModbusPduHdr);

	switch (pduHdr.m_func)
	{
	case io.ModbusFunc.ReadCoils:
	case io.ModbusFunc.ReadDiscreteInputs:
	case io.ModbusFunc.ReadHoldingRegisters:
	case io.ModbusFunc.ReadInputRegisters:
		return
			role == ModbusRole.Master ? sizeof (ModbusReadPdu) :
			size < sizeof (ModbusReadReplyPdu) ? sizeof (ModbusReadReplyPdu) :
			sizeof (ModbusReadReplyPdu) + ((ModbusReadReplyPdu const*) pduHdr).m_size;

	case io.ModbusFunc.WriteCoil:
	case io.ModbusFunc.WriteRegister:
		return sizeof (ModbusWritePdu);

	case io.ModbusFunc.WriteMultipleCoils:
	case io.ModbusFunc.WriteMultipleRegisters:
		return
			role == ModbusRole.Slave ? sizeof (ModbusWriteMultipleReplyPdu) :
			size < sizeof (ModbusWriteMultiplePdu) ? sizeof (ModbusWriteMultiplePdu) :
			sizeof (ModbusWriteMultiplePdu) + ((ModbusWriteMultiplePdu const*) pduHdr).m_size;

	case io.ModbusFunc.MaskWriteRegister:
		return sizeof (ModbusMaskWritePdu);

	case io.ModbusFunc.ReadExceptionStatus:
	case io.ModbusFunc.Diagnostics:
	case io.ModbusFunc.GetCommEventCounter:
	case io.ModbusFunc.GetCommEventLog:
	case io.ModbusFunc.ReportServerId:
	case io.ModbusFunc.ReadFileRecord:
	case io.ModbusFunc.WriteFileRecord:
	case io.ModbusFunc.ReadWriteMultipleRegisters:
	case io.ModbusFunc.ReadFifoQueue:
	case io.ModbusFunc.EncapsulatedInterface:
		// TODO: process these function codes

		std.setError ("Unsupported Modbus function");
		return -1;

	default:
		if (pduHdr.m_func & ModbusFunc.ExceptionFlag)
			return sizeof (ModbusExceptionPdu);

		std.setError ("Unknown Modbus function");
		return -1;
	}
}

//..............................................................................

// Modbus ADUs

struct ModbusRtuAduHdr
{
	uint8_t m_deviceAddress;

	// followed by PDU and CRC16 checksum
}

struct ModbusAsciiAduHdr
{
	char m_deviceAddress [2];

	// followed by hex-encoded PDU, LRC8 checksum, and then \r\n
}

struct ModbusTcpAduHdr
{
	bigendian uint16_t m_transactionId;
	bigendian uint16_t m_protocol;
	bigendian uint16_t m_length; // length of data following this field
	uint8_t m_deviceAddress;

	// followed by PDU
}

// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

enum
{
	ModbusMaxRtuAduSize   = sizeof (ModbusRtuAduHdr) + ModbusMaxPduSize + 2,
	ModbusMaxAsciiAduSize = 1 + sizeof (ModbusAsciiAduHdr) + ModbusMaxPduSize * 2 + 4,
	ModbusMaxTcpAduSize = sizeof (ModbusTcpAduHdr) + ModbusMaxPduSize,
}

//..............................................................................

// calculates the actual size of Modbus RTU ADU

size_t errorcode getModbusRtuAduSize (
	ModbusRole role,
	ModbusRtuAduHdr const* aduHdr,
	size_t size
	)
{
	if (size < sizeof (ModbusRtuAduHdr))
		return sizeof (ModbusRtuAduHdr);

	ModbusPduHdr const* pduHdr = (ModbusPduHdr const*) (aduHdr + 1);
	size_t pduSize = getModbusPduSize (role, pduHdr, size - sizeof (ModbusRtuAduHdr));
	return pduSize + sizeof (uint8_t) + sizeof (uint16_t);
}

//..............................................................................

} // namespace io {
