//..............................................................................
//
// ModBus RTU Protocol Analyzer layer
//

import "io_ModBus.jnc"
import "doc_Plugin.jnc"
import "crc16.jnc"

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

class ModBusRtuLayer:
	doc.Layer,
	log.Converter
{
	// checking for correct modbus delays (1.5/3.5 chars) is not possible
	// due to  insufficient precision of timestamps	(we get timestamp of read
	// completion and no timestamps of individual bytes)

	enum: uint64_t
	{
		MinInterFrameDelay = 500 * 10000, // 500 ms
	}

protected:
	uint_t m_lastRecordCode;
	uint64_t m_lastRecordTimestamp;
	void const* m_dataBuffer;
	size_t m_dataSize;

public:
	construct (doc.PluginHost* pluginHost);

	override bool convert (
		log.Writer* writer,
		uint64_t timestamp,
		uint_t recordCode,
		void const* p,
		size_t size
		);

protected:
	bool convertImpl (
		log.Writer* writer,
		uint64_t timestamp,
		uint_t recordCode,
		io.ModBusRole role,
		void const* p,
		size_t size
		);
}

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

ModBusRtuLayer.construct (doc.PluginHost* pluginHost)
{
	basetype1.construct (pluginHost);

	m_lastRecordCode = 0;

	pluginHost.m_log.attachConverter (this, representModBusRtuLog);
}

bool ModBusRtuLayer.convert (
	log.Writer* writer,
	uint64_t timestamp,
	uint_t recordCode,
	void const* p,
	size_t size
	)
{
	bool result;
	io.ModBusRole role;

	switch (recordCode)
	{
	case log.StdRecordCode.Tx:
		role = io.ModBusRole.Master;
		break;

	case log.StdRecordCode.Rx:
		role = io.ModBusRole.Slave;
		break;

	default:
		return false;
	}

	bool isNewFrame =
		recordCode != m_lastRecordCode ||
		(timestamp - m_lastRecordTimestamp) >= MinInterFrameDelay;

	if (isNewFrame)
	{
		result = convertImpl (writer, timestamp, recordCode, role, p, size);
		if (result)
		{
			m_dataBuffer = null;
			m_dataSize = 0;
		}
		else // save for possible frame continuation...
		{
			m_dataBuffer = memdup (p, size);
			m_dataSize = size;
		}
	}
	else
	{
		m_dataBuffer = memcat (m_dataBuffer, m_dataSize, p, size);
		m_dataSize += size;

		result = convertImpl (writer, timestamp, recordCode, role, m_dataBuffer, m_dataSize);
		if (result)
		{
			m_dataBuffer = null;
			m_dataSize = 0;
		}
	}

	m_lastRecordCode = recordCode;
	m_lastRecordTimestamp = timestamp;
	return result;
}

bool ModBusRtuLayer.convertImpl (
	log.Writer* writer,
	uint64_t timestamp,
	uint_t recordCode,
	io.ModBusRole role,
	void const* p,
	size_t size
	)
{
	io.ModBusRtuAduHdr const* aduHdr = (io.ModBusRtuAduHdr const*) p;
	size_t aduSize = io.getModBusRtuAduSize (role, aduHdr, size);

	if (aduSize == -1 || size < aduSize)
		return false;

	size_t checksumOffset = aduSize - sizeof (uint16_t);
	uint16_t correctChecksum = crc16_ansi (p, checksumOffset, 0xffff);
	uint16_t actualChecksum = *(uint16_t const*) (p + checksumOffset);

	if (actualChecksum != correctChecksum)
		return false;

	writer.write (timestamp, recordCode, p, size);

	ModBusRtuLogPacket packet;
	packet.m_role = role;
	packet.m_deviceAddress = aduHdr.m_deviceAddress;
	packet.m_checksum = actualChecksum;

	size_t pduSize = aduSize - sizeof (io.ModBusRtuAduHdr) - sizeof (uint16_t);
	void const* params = memcat (&packet, sizeof (packet), aduHdr + 1, pduSize);
	writer.write (timestamp, ModBusRtuLogRecordCode.Packet, params, sizeof (packet) + pduSize);

	return true;
}

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