//..............................................................................
//
// Modbus RTU stream parser
//

import "ModbusLogRecordCode.jnc"

class ModbusRtuParser: ModbusParserBase {
	// checking for correct serial modbus delays (1.5/3.5 chars) is not possible
	// due to insufficient precision of timestamps -- we get timestamps of read
	// completion and no timestamps of individual bytes

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

protected:
	size_t m_expectedSize;

public:
	construct(ModbusInfoSet* infoSet) {
		basetype.construct(io.ModbusProtocol.Rtu, infoSet);
	}

	override parse(
		log.Writer* writer,
		uint64_t timestamp,
		uint64_t originalRecordCode,
		void const* p,
		size_t size
	);

	override reset();

protected:
	ModbusLogRecordCode getPacketRecordCode() {
		return m_modbusRole == io.ModbusRole.Master ?
			ModbusLogRecordCode.MasterPacket_Rtu :
			ModbusLogRecordCode.SlavePacket_Rtu;
	}
}

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

void ModbusRtuParser.parse(
	log.Writer* writer,
	uint64_t timestamp,
	uint64_t originalRecordCode,
	void const* p,
	size_t size
) {
	if (m_dataBuffer.m_size && timestamp - m_lastTimestamp > MinInterFrameDelay) {
		std.setError(
			$"Incomplete Modbus RTU packet: \e[31m%1\e[m out of \e[31m%2\e[m bytes" (
			m_dataBuffer.m_size,
			m_expectedSize
		));

		writer.writeLastError(timestamp, ModbusLogRecordCode.ParseError);
		m_infoSet.addError();
		reset();
	}

	updateLastTimestamp(timestamp);

	void const* p0 = p;
	void const* end = p + size;

nestedscope:
	ModbusLogRecordCode recordCode = getPacketRecordCode();

	while (p < end) {
		size = end - p;
		if (!m_expectedSize)
			m_expectedSize = io.getModbusRtuAduSize(m_modbusRole, (io.ModbusRtuAduHdr const*)p, size);

		size_t leftoverSize = m_expectedSize - m_dataBuffer.m_size;
		if (size < leftoverSize) {
			m_dataBuffer.append(p, size);
			break;
		}

		m_dataBuffer.append(p, leftoverSize);
		p += leftoverSize;

		// check ADU size once again (as we have appended some data)

		io.ModbusRtuAduHdr const* aduHdr = (io.ModbusRtuAduHdr const*)m_dataBuffer.m_p;
		size_t expectedSize = io.getModbusRtuAduSize(m_modbusRole, aduHdr, m_dataBuffer.m_size);
		if (expectedSize > m_expectedSize) {
			m_expectedSize = expectedSize;
			continue; // ADU has grown, try again
		}

		// ADU completed

		writer.write(timestamp, originalRecordCode, p0, p - p0);
		p0 = p;

		writer.write(timestamp, recordCode, m_dataBuffer.m_p, m_dataBuffer.m_size);
		m_infoSet.addAdu(m_modbusRole);

		if (m_mode == ModbusParserMode.Alternate) {
			m_modbusRole = aduHdr.m_deviceAddress != 0 ? // write-only broadcast from master
				(io.ModbusRole) !m_modbusRole :
				io.ModbusRole.Master;

			recordCode = getPacketRecordCode();
		}

		m_dataBuffer.clear();
		m_expectedSize = 0;
	}

	if (p0 < end)
		writer.write(timestamp, originalRecordCode, p0, end - p0);

catch:
	if (p0 < end)
		writer.write(timestamp, originalRecordCode, p0, end - p0);

	writer.writeLastError(timestamp, ModbusLogRecordCode.ParseError);
	m_infoSet.addError();
	reset();
}

void ModbusRtuParser.reset() {
	m_dataBuffer.clear();
	m_expectedSize = 0;

	if (m_mode == ModbusParserMode.Alternate)
		m_modbusRole = io.ModbusRole.Master;
}

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