//..............................................................................
//
// UDP Flow layer
//

import "doc_Layer.jnc"
import "ui_History.jnc"
import "../NetSnifferLog/NetSnifferLogRecordCode.jnc"
import "../EthernetTap/EthernetTapLogRecordCode.jnc"

using extension ui.ComboBoxHistory;

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

class UdpFlowLayer:
	doc.Layer,
	log.Converter {
protected:
	// udp decoding

	UdpDecoder m_decoder;
	UdpFlowLogFilter m_filter;

	// toolbar, status bar & menu

	ui.ComboBox* m_viewFilterCombo;

	// property grid

	ui.StringProperty* m_viewFilterProp;
	ui.IntProperty* m_ipFragmentLimitProp;
	ui.IntProperty* m_ipFragmentTimeoutProp;

public:
	construct(doc.PluginHost* pluginHost);

	override void load(doc.Storage* storage);
	override void save(doc.Storage* storage);
	override void updateProperties();
	override bool errorcode applyProperties();
	override void restoreDefaultProperties();

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

	override void reset() {
		m_decoder.reset();
	}

protected:
	void onSetViewFilter() {
		try applyViewFilter(true);
	}

	bool errorcode applyViewFilter(bool rebuildLog = false);
	void createUi();
}

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

UdpFlowLayer.construct(doc.PluginHost* pluginHost) {
	basetype.construct(pluginHost);

	createUi();

	pluginHost.m_log.addConverter(this);
	pluginHost.m_log.addFilter(m_filter);
}

void UdpFlowLayer.createUi() {
	// toolbar

	m_pluginHost.m_toolBar.addLabel("Filter:");
	m_viewFilterCombo = m_pluginHost.m_toolBar.addComboBox(-1);
	m_viewFilterCombo.m_isEditable = true;
	m_viewFilterCombo.m_onEnter += onSetViewFilter;
	m_viewFilterCombo.m_placeholderText = "Enter a filter address...";
	m_viewFilterCombo.m_toolTipText = "Only show packets matching this address";

	ui.Action* action = createAction("Apply filter", "images/apply.png");
	action.m_onTriggered += onSetViewFilter;

	// property grid

	m_viewFilterProp = m_pluginHost.m_propertyGrid.createStringProperty(
		"View filter",
		"Enter a filter address...",
		"Only show packets matching this address"
	);

	m_ipFragmentLimitProp  = m_pluginHost.m_propertyGrid.createIntProperty(
		"IP fragment limit",
		"Specify the maximum number of IP fragments"
	);

	m_ipFragmentTimeoutProp  = m_pluginHost.m_propertyGrid.createIntProperty(
		"IP fragment timeout (ms)",
		"Specify the maximum delay between IP fragments"
	);
}

bool errorcode UdpFlowLayer.applyViewFilter(bool rebuildLog) {
	m_pluginHost.m_log.suspend();
	bool result = try m_filter.setFilter(m_viewFilterCombo.m_editText);
	m_pluginHost.m_log.resume();

	if (!result) {
		m_viewFilterCombo.m_backgroundColor = ui.StdColor.PastelRed;
		return false;
	}

	m_viewFilterCombo.addEditTextToHistory();
	m_viewFilterCombo.m_backgroundColor = ui.ColorFlags.Transparent;

	if (rebuildLog)
		m_pluginHost.m_log.rebuild();

	return true;
}

void UdpFlowLayer.load(doc.Storage* storage) {
	m_viewFilterCombo.loadHistory(storage, "viewFilterHistory");
	m_viewFilterCombo.m_editText = storage.readString("viewFilter");
	m_ipFragmentLimitProp.m_value = storage.readInt("ipFragmentLimit", io.IpDefragmenter.Defaults.FragmentLimit);
	m_ipFragmentTimeoutProp.m_value = storage.readInt("ipFragmentTimeout", io.IpDefragmenter.Defaults.FragmentTimeout);

	m_decoder.setupDefragmenter(m_ipFragmentLimitProp.m_value, m_ipFragmentTimeoutProp.m_value);
	try applyViewFilter();
}

void UdpFlowLayer.save(doc.Storage* storage) {
	m_viewFilterCombo.saveHistory(storage, "viewFilterHistory");
	storage.writeString("viewFilter", m_viewFilterCombo.m_editText);
	storage.writeInt("ipFragmentLimit", m_ipFragmentLimitProp.m_value);
	storage.writeInt("ipFragmentTimeout", m_ipFragmentTimeoutProp.m_value);
}

void UdpFlowLayer.updateProperties() {
	m_viewFilterProp.m_value = m_viewFilterCombo.m_editText;
}

bool errorcode UdpFlowLayer.applyProperties() {
	m_viewFilterCombo.m_editText = m_viewFilterProp.m_value;
	m_decoder.setupDefragmenter(m_ipFragmentLimitProp.m_value, m_ipFragmentTimeoutProp.m_value);
	applyViewFilter();
	return true;
}

void UdpFlowLayer.restoreDefaultProperties() {
	m_ipFragmentLimitProp.m_value = io.IpDefragmenter.Defaults.FragmentLimit;
	m_ipFragmentTimeoutProp.m_value = io.IpDefragmenter.Defaults.FragmentTimeout;
}

bool UdpFlowLayer.convert(
	log.Writer* writer,
	uint64_t timestamp,
	uint64_t recordCode,
	void const* p,
	size_t size
) {
	g_logWriter = writer;

	bool result = true;

	switch (recordCode) {
	case log.StdRecordCode.SessionStarted:
		m_decoder.reset();
		result = false;
		break;

	case NetSnifferLogRecordCode.EthernetPacket:
		m_decoder.decodeLinkHdr(io.PcapLinkType.Ethernet, timestamp, p, size);
		break;

	case NetSnifferLogRecordCode.Packet:
		io.PcapLinkType linkType = *(io.PcapLinkType const*)p;
		p += sizeof(io.PcapLinkType);
		size -= sizeof(io.PcapLinkType);
		m_decoder.decodeLinkHdr(linkType, timestamp, p, size);
		break;

	case EthernetTapLogRecordCode.Packet_ch1:
	case EthernetTapLogRecordCode.Packet_ch2:
		PacketHdr const* packetHdr = (PacketHdr const*)p;
		if (!(packetHdr.m_flags & PacketFlags.CrcValid))
			break; // ignore bad frames

		p += sizeof(PacketHdr);
		size -= sizeof(PacketHdr);
		m_decoder.decodeLinkHdr(io.PcapLinkType.Ethernet, timestamp, p, size);
		break;

	default:
		result = false;
	}

	g_logWriter = null; // don't retain the conversion writer
	return result;
}

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