//..............................................................................
//
// TCP Monitor session
//

import "io_base.jncx"
import "io_pcap.jncx"
import "io_TcpIp.jnc"
import "io_SocketAddress.jnc"
import "io_SocketAddressResolver.jnc"
import "std_Buffer.jnc"
import "doc_Plugin.jnc"
import "gui_Dialog.jnc"
import "gui_History.jnc"

using extension gui.ComboBoxHistory;

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

class TcpFlowMonSession: doc.Session
{
protected:
	enum Defaults
	{
		Promiscious  = false,
		SnapshotSize = 8 * 1024,
		ReadTimeout  = 200,
	}

	bitflag enum State
	{
		Resolving,
		Capturing,
		TcpConnection,
		TcpSyn,
	}

	enum ActionId
	{
		Capture,
		_Count,
	}

	enum IconId
	{
		Capture,
		StopCapture,
		_Count,
	}

	enum StatusPaneId
	{
		State,
		_Count,
	}

	class TcpPeer
	{
		io.SocketAddress m_address;
		io.TcpSequencer m_tcpSequencer;
	}

protected:
	State bindable m_state;

	// pending capture (post-resolve)

	io.PcapDeviceDesc const* m_pendingDevice;
	char const* m_pendingFileName;

	// pcap & tcp decoding

	io.Pcap m_pcap;
	io.IpDefragmenter m_ipDefragmenter;
	io.SocketAddressResolver m_resolver;
	io.SocketAddress m_filterAddress;
	TcpPeer m_tcpPeer1;
	TcpPeer m_tcpPeer2;
	std.Buffer m_readBuffer;
	uint_t m_resolverSyncId;
	uint_t m_pcapSyncId;

	// toolbar, status bar & menu

	gui.Action* m_actionTable [ActionId._Count];
	gui.Icon* m_iconTable [IconId._Count];
	gui.StatusPane* m_statusPaneTable [StatusPaneId._Count];
	gui.ComboBox* m_deviceCombo;
	gui.ComboBox* m_filterCombo;
	gui.FileDlg* m_fileDlg;

	// property grid

	gui.EnumProperty* m_deviceProp;
	gui.BoolProperty* m_promisciousProp;
	gui.StringProperty* m_filterProp;
	gui.IntProperty* m_tcpOutOfSeqLimitProp;
	gui.IntProperty* m_ipFragmentLimitProp;
	gui.IntProperty* m_ipFragmentTimeoutProp;
	gui.IntProperty* m_snapshotSizeProp;
	io.BufferPropertySet m_bufferPropertySet;

public:
	construct (doc.PluginHost* pluginHost);

	bool errorcode capture ();

	bool errorcode capture (
		io.PcapDeviceDesc const* device,
		io.SocketAddress const* filterAddress
		);

	bool errorcode openCapFile (
		char const* fileName,
		io.SocketAddress const* filterAddress
		);

	stopCapture ();

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

protected:
	onCapture ()
	{
		try (m_state ? stopCapture () : capture ());
	}

	onSetFilter ()
	{
		try (m_state ? applyFilter () : capture ());
	}

	onDeviceComboEnter ()
	{
		try capture ();
	}

	onFileDlgCompleted (gui.StdDlgButton button);

	onResolveCompleted (
		uint_t syncId,
		io.SocketAddress const* addressTable,
		size_t addressCount,
		std.Error const* error
		);

	onPcapEvent (
		uint_t syncId,
		io.PcapEvents triggeredEvents
		);

	onData (
		uint_t recordCode,
		void const* p,
		size_t size
		);

	onDataDrop ();

	char const* createFilterString (io.SocketAddress const* filterAddress);
	bool errorcode resolve (char const* addressString);

	bool errorcode applyFilter ();
	bool errorcode setFilter (io.SocketAddress const* filterAddress);

	bool decodePacket (
		void const* p,
		size_t size
		);

	decodeTcpPacket (
		TcpPeer* peer,
		io.TcpHdr const* tcpHdr,
		void const* p,
		size_t size
		);

	waitPcapEvents ();
	createUi ();

	reactor m_uiReactor ();
}

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

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

	pluginHost.m_log.attachRepresenter (representTcpFlowMonLog);

	m_state = 0;
	m_tcpPeer1.m_tcpSequencer.m_onData = onData ~(log.StdRecordCode.Rx);
	m_tcpPeer1.m_tcpSequencer.m_onDataDrop = onDataDrop;
	m_tcpPeer2.m_tcpSequencer.m_onData = onData ~(log.StdRecordCode.Tx);
	m_tcpPeer2.m_tcpSequencer.m_onDataDrop = onDataDrop;

	createUi ();
}

TcpFlowMonSession.createUi ()
{
	// toolbar

	m_pluginHost.m_toolBar.addLabel ("Device:");
	m_deviceCombo = io.createPcapDeviceComboBox (m_pluginHost.m_toolBar);
	m_deviceCombo.m_onEnter += onDeviceComboEnter;

	m_pluginHost.m_toolBar.addLabel ("Filter:");
	m_filterCombo = m_pluginHost.m_toolBar.addComboBox (-1);
	m_filterCombo.m_isEditable = true;
	m_filterCombo.m_onEnter += onSetFilter;

	m_iconTable [IconId.Capture] = new gui.Icon ("images/capture.png");
	m_iconTable [IconId.StopCapture] = new gui.Icon ("images/pause.png");

	m_actionTable [ActionId.Capture] = createAction ("Capture", m_iconTable [IconId.Capture]);
	m_actionTable [ActionId.Capture].m_onTriggered += onCapture;

	// status bar

	m_statusPaneTable [StatusPaneId.State] = m_pluginHost.m_statusBar.addPane (, 80);

	// property grid

	gui.GroupProperty* group = m_pluginHost.m_propertyGrid.createGroupProperty (
		"Connection",
		"Pcap-specific connection settings"
		);

	m_deviceProp = io.createPcapDeviceProperty (
		m_pluginHost.m_propertyGrid,
		group,,,
		"Device",
		"Specify the device to capture on"
		);

	m_promisciousProp = m_pluginHost.m_propertyGrid.createBoolProperty (
		group,,
		"Promiscious mode",
		"Put the capturing interface into promiscious mode"
		);

	m_filterProp = m_pluginHost.m_propertyGrid.createStringProperty (
		group,,
		"Address filter",
		"Specify the address to monitor"
		);

	group = m_pluginHost.m_propertyGrid.createGroupProperty (
		"TCP defragmenter",
		"TCP defragmentation settings"
		);

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

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

	m_tcpOutOfSeqLimitProp = m_pluginHost.m_propertyGrid.createIntProperty (
		group,,
		"TCP out-of-seq limit",
		"Specify the maximum number of out-of-order TCP segments"
		);

	group = m_pluginHost.m_propertyGrid.createGroupProperty (
		"Buffering",
		"Advanced buffering options affecting throughtput and compatibility"
		);

	m_snapshotSizeProp = m_pluginHost.m_propertyGrid.createIntProperty (
		group,,
		"Snapshot size",
		"Specify the Pcap snapshot size"
		);

	m_snapshotSizeProp.m_spinBoxStep = 1024;

	io.BufferPropertySet.PropertyId ids =
		io.BufferPropertySet.PropertyId.ReadBufferSize |
		io.BufferPropertySet.PropertyId.NotifyReadBufferFull;

	m_bufferPropertySet.createProperties (m_pluginHost.m_propertyGrid, group,, ids);

	m_uiReactor.start ();
}

TcpFlowMonSession.onFileDlgCompleted (gui.StdDlgButton button)
{
	if (button != gui.StdDlgButton.Ok)
		return;

	char const* fileName = m_fileDlg.m_filePath;
	char const* addressString = m_filterCombo.m_editText;
	m_filterCombo.addEditTextToHistory ();

	io.SocketAddress filterAddress;
	bool isDirectAddress = try filterAddress.parse (addressString);
	if (isDirectAddress)
	{
		try openCapFile (fileName, &filterAddress);
	}
	else
	{
		m_pendingDevice = null;
		m_pendingFileName = fileName;
		try resolve (addressString);
	}
}

bool errorcode TcpFlowMonSession.capture ()
{
	stopCapture ();

	if (m_deviceCombo.m_currentIndex == -1)
		return false;

	io.PcapDeviceDesc const* device = m_deviceCombo.m_currentData;
	if (!device)
	{
		if (!m_fileDlg)
		{
			m_fileDlg = new gui.FileDlg;
			m_fileDlg.m_onCompleted += onFileDlgCompleted;
		}

		m_fileDlg.m_kind = gui.FileDlgKind.Open;
		m_fileDlg.m_title = "Open Wireshark File (*.cap; *.pcap)...";
		m_fileDlg.m_filter = "Wireshark .cap files (*.cap; *.pcap);; All files (*.*)";
		m_fileDlg.show ();
		return true;
	}

	char const* addressString = m_filterCombo.m_editText;
	m_filterCombo.addEditTextToHistory ();

	io.SocketAddress filterAddress;
	bool isDirectAddress = try filterAddress.parse (addressString);
	if (isDirectAddress)
		return try capture (device, &filterAddress);

	m_pendingDevice = device;
	m_pendingFileName = null;
	return try resolve (addressString);
}

bool errorcode TcpFlowMonSession.capture (
	io.PcapDeviceDesc const* device,
	io.SocketAddress const* filterAddress
	)
{
	stopCapture ();
	startSession ();

	char const* filterString = createFilterString (filterAddress);
	writeLogString (TcpFlowMonLogRecordCode.StartingCapture, filterString);

	m_pcap.m_snapshotSize = m_snapshotSizeProp.m_value;
	m_pcap.m_readBufferSize = m_bufferPropertySet.m_readBufferSizeProp.m_value;

	bool result = try m_pcap.openDevice (
		device.m_name,
		filterString,
		m_promisciousProp.m_value
		);

	if (!result)
	{
		writeLogLastError (TcpFlowMonLogRecordCode.CaptureError);
		return false;
	}

	m_state = State.Capturing;
	m_filterAddress = *filterAddress;
	writeLog (TcpFlowMonLogRecordCode.CaptureStarted);
	waitPcapEvents ();
	return true;
}

bool errorcode TcpFlowMonSession.openCapFile (
	char const* fileName,
	io.SocketAddress const* filterAddress
	)
{
	char const* filterString = createFilterString (filterAddress);
	writeLogString (TcpFlowMonLogRecordCode.StartingCapture, filterString);

	m_pcap.m_snapshotSize = m_snapshotSizeProp.m_value;
	m_pcap.m_readBufferSize = m_bufferPropertySet.m_readBufferSizeProp.m_value;

	bool result = try m_pcap.openFile (fileName, filterString);
	if (!result)
	{
		writeLogLastError (TcpFlowMonLogRecordCode.CaptureError);
		return false;
	}

	m_state = State.Capturing;
	m_filterAddress = *filterAddress;
	writeLog (TcpFlowMonLogRecordCode.CaptureStarted);
	waitPcapEvents ();
	return true;
}

TcpFlowMonSession.stopCapture ()
{
	if (!m_state)
		return;

	if (m_state & State.Resolving)
		writeLog (TcpFlowMonLogRecordCode.ResolveCancelled);

	if (m_state & State.Capturing)
		writeLog (TcpFlowMonLogRecordCode.CaptureStopped);

	m_pcap.close ();
	m_ipDefragmenter.reset ();
	m_state = 0;
	m_resolverSyncId++;
	m_pcapSyncId++;
}

bool errorcode TcpFlowMonSession.resolve (char const* addressString)
{
	writeLogString (TcpFlowMonLogRecordCode.Resolving, addressString);

	bool result = try m_resolver.resolve (
		addressString,
		onResolveCompleted ~(++m_resolverSyncId) @ m_pluginHost.m_mainThreadScheduler
		);

	if (!result)
	{
		writeLogLastError (TcpFlowMonLogRecordCode.ResolveError);
		return false;
	}

	m_state = State.Resolving;
	return true;
}

bool errorcode TcpFlowMonSession.applyFilter ()
{
	char const* filterString = m_filterCombo.m_editText;
	m_filterCombo.addEditTextToHistory ();

	io.SocketAddress filterAddress;
	bool isDirectAddress = try filterAddress.parse (filterString);
	if (isDirectAddress)
	{
		m_state &= ~State.Resolving;
		return try setFilter (&filterAddress);
	}

	writeLogString (TcpFlowMonLogRecordCode.Resolving, filterString);

	bool result = try m_resolver.resolve (
		filterString,
		onResolveCompleted ~(++m_resolverSyncId) @ m_pluginHost.m_mainThreadScheduler
		);

	if (!result)
	{
		writeLogLastError (TcpFlowMonLogRecordCode.ResolveError);
		return false;
	}

	m_state |= State.Resolving;
	return true;
}

bool errorcode TcpFlowMonSession.setFilter (io.SocketAddress const* filterAddress)
{
	if (!m_state.Capturing)
		return true;

	char const* filterString = createFilterString (filterAddress);
	writeLogString (TcpFlowMonLogRecordCode.ChangingFilter, filterString);
	bool result = try m_pcap.setFilter (filterString);
	if (!result)
	{
		writeLogLastError (TcpFlowMonLogRecordCode.FilterError);
		return false;
	}

	writeLog (TcpFlowMonLogRecordCode.FilterChanged);
	return true;
}

char const* TcpFlowMonSession.createFilterString (io.SocketAddress const* filterAddress)
{
	char const* filterString =
		filterAddress.m_family == io.AddressFamily.Ip6 ? $"tcp and host $(filterAddress.m_ip6.m_address.getString ())" :
		filterAddress.m_ip4.m_address.m_i32 ? $"tcp and host $(filterAddress.m_ip4.m_address.getString ())" :
		"tcp";

	// fragmented segments will not satisfy port filter

	// if (filterAddress.m_ip4.m_port)
	//	filterString = $"$filterString and port $(filterAddress.m_ip4.m_port)";

	return filterString;
}

TcpFlowMonSession.waitPcapEvents ()
{
	io.PcapEvents eventMask =
		io.PcapEvents.IoError |
		io.PcapEvents.ReadBufferFull |
		io.PcapEvents.IncomingData |
		io.PcapEvents.Eof;

	m_pcap.wait (eventMask, onPcapEvent ~(++m_pcapSyncId) @ m_pluginHost.m_mainThreadScheduler);
}

void TcpFlowMonSession.load (doc.Storage* storage)
{
	m_filterCombo.loadHistory (storage, "captureFilterHistory");
	m_deviceCombo.m_currentIndex = storage.readInt ("deviceIdx");
	m_promisciousProp.m_value = storage.readBool ("promiscious");
	m_filterCombo.m_editText = storage.readString ("captureFilter");
	m_ipFragmentLimitProp.m_value = storage.readInt ("ipFragmentLimit", io.IpDefragmenter.DefIpFragmentLimit);
	m_ipFragmentTimeoutProp.m_value = storage.readInt ("ipFragmentTimeout", io.IpDefragmenter.DefIpFragmentTimeout);
	m_tcpOutOfSeqLimitProp.m_value = storage.readInt ("tcpOutOfSeqLimit", io.TcpSequencer.DefOutOfSeqLimit);
	m_snapshotSizeProp.m_value = storage.readInt ("snapshotSize", Defaults.SnapshotSize);

	m_bufferPropertySet.load (storage);
	m_readBuffer.setSize (m_bufferPropertySet.m_readBufferSizeProp.m_value);
}

void TcpFlowMonSession.save (doc.Storage* storage)
{
	m_bufferPropertySet.save (storage);
	m_filterCombo.saveHistory (storage, "captureFilterHistory");
	storage.writeInt ("deviceIdx", (int) m_deviceCombo.m_currentIndex);
	storage.writeBool ("promiscious", m_promisciousProp.m_value);
	storage.writeString ("captureFilter", m_filterCombo.m_editText);
	storage.writeInt ("ipFragmentLimit", m_ipFragmentLimitProp.m_value);
	storage.writeInt ("ipFragmentTimeout", m_ipFragmentTimeoutProp.m_value);
	storage.writeInt ("tcpOutOfSeqLimit", m_tcpOutOfSeqLimitProp.m_value);
	storage.writeInt ("snapshotSize", m_snapshotSizeProp.m_value);
}

void TcpFlowMonSession.updateProperties ()
{
	m_deviceProp.m_currentIndex = m_deviceCombo.m_currentIndex;
	m_filterProp.m_value = m_filterCombo.m_editText;
}

bool errorcode TcpFlowMonSession.applyProperties ()
{
	m_deviceCombo.m_currentIndex = m_deviceProp.m_currentIndex;
	m_filterCombo.m_editText = m_filterProp.m_value;
	m_ipDefragmenter.m_fragmentLimit = m_ipFragmentLimitProp.m_value;
	m_ipDefragmenter.m_timeout = m_ipFragmentTimeoutProp.m_value;
	m_tcpPeer1.m_tcpSequencer.m_outOfSeqLimit = m_tcpOutOfSeqLimitProp.m_value;
	m_tcpPeer2.m_tcpSequencer.m_outOfSeqLimit = m_tcpOutOfSeqLimitProp.m_value;
	m_readBuffer.setSize (m_bufferPropertySet.m_readBufferSizeProp.m_value);

	if (!m_pcap.m_isOpen)
		return true;

	m_pcap.m_snapshotSize = m_snapshotSizeProp.m_value;
	m_pcap.m_readBufferSize = m_bufferPropertySet.m_readBufferSizeProp.m_value;
	applyFilter ();
	return true;
}

void TcpFlowMonSession.restoreDefaultProperties ()
{
	m_deviceProp.m_currentIndex = 0;
	m_promisciousProp.m_value = Defaults.Promiscious;
	m_snapshotSizeProp.m_value = Defaults.SnapshotSize;
	m_ipFragmentLimitProp.m_value = io.IpDefragmenter.DefIpFragmentLimit;
	m_ipFragmentTimeoutProp.m_value = io.IpDefragmenter.DefIpFragmentTimeout;
	m_tcpOutOfSeqLimitProp.m_value = io.TcpSequencer.DefOutOfSeqLimit;
	m_snapshotSizeProp.m_value = Defaults.SnapshotSize;
	m_bufferPropertySet.restoreDefaults ();
}

TcpFlowMonSession.onResolveCompleted (
	uint_t syncId,
	io.SocketAddress const* addressTable,
	size_t addressCount,
	std.Error const* error
	)
{
	if (syncId != m_resolverSyncId)
		return;

	m_state &= ~State.Resolving;

	if (error)
	{
		writeLogError (TcpFlowMonLogRecordCode.ResolveError, error);
		return;
	}

	writeLog (
		TcpFlowMonLogRecordCode.ResolveCompleted,
		addressTable,
		sizeof (io.SocketAddress)
		);

	if (m_state & State.Capturing)
		try setFilter (addressTable);
	else if (m_pendingDevice)
		try capture (m_pendingDevice, addressTable);
	else if (m_pendingFileName)
		try openCapFile (m_pendingFileName, addressTable);
}

TcpFlowMonSession.onPcapEvent (
	uint_t syncId,
	io.PcapEvents triggeredEvents
	)
{
	if (syncId != m_pcapSyncId)
		return;

	if (triggeredEvents & io.PcapEvents.IoError)
	{
		writeLog (log.StdRecordCode.Error, m_pcap.m_ioError, m_pcap.m_ioError.m_size);
		stopCapture ();
		return;
	}

	if ((triggeredEvents & io.PcapEvents.ReadBufferFull) &&
		m_bufferPropertySet.m_notifyReadBufferFullProp.m_value)
		writeLog (log.StdRecordCode.RxBufferFull);

	if (triggeredEvents & io.PcapEvents.IncomingData)
	{
		size_t result = m_pcap.read (m_readBuffer.m_p, m_readBuffer.m_size);
		if (result)
			decodePacket (m_readBuffer.m_p, result);
	}

	if (triggeredEvents & io.PcapEvents.Eof)
	{
		stopCapture ();
		return;
	}

	waitPcapEvents ();
}

TcpFlowMonSession.onData (
	uint_t recordCode,
	void const* p,
	size_t size
	)
{
	writeLog (recordCode, p, size);
}

TcpFlowMonSession.onDataDrop ()
{
	writeLog (TcpFlowMonLogRecordCode.OutOfOrderDataDropped);
}

bool TcpFlowMonSession.decodePacket (
	void const* p,
	size_t size
	)
{
	if (size < sizeof (io.EthernetHdr))
		return false;

	io.EthernetHdr const* ethernetHdr = (io.EthernetHdr const*) p;
	p += sizeof (io.EthernetHdr);
	size -= sizeof (io.EthernetHdr);

	io.SocketAddress srcAddress;
	io.SocketAddress dstAddress;

	switch (ethernetHdr.m_type)
	{
	case io.EthernetType.Ip:
		if (size < sizeof (io.IpHdr))
			return false;

		io.IpHdr const* ipHdr = (io.IpHdr const*) p;
		size_t ipHdrSize = ipHdr.m_headerLength * 4;

		if (ipHdr.m_protocol != io.Protocol.Tcp ||
			ipHdrSize < sizeof (io.IpHdr) ||
			ipHdrSize > ipHdr.m_totalLength ||
			ipHdr.m_totalLength > size)
			return false;

		if ((ipHdr.m_flags & io.IpFlags.Mf) || ipHdr.m_fragmentOffset) // fragmented ip
		{
			ipHdr = m_ipDefragmenter.defragment (ipHdr, size);
			if (!ipHdr)
				return false;

			p = ipHdr + 1;
			size = dynamic sizeof (p);
		}
		else
		{
			p += ipHdrSize;
			size = ipHdr.m_totalLength - ipHdrSize;
		}

		srcAddress.m_family = io.AddressFamily.Ip4;
		srcAddress.m_ip4.m_address.m_i32 = ipHdr.m_srcAddress;
		dstAddress.m_family = io.AddressFamily.Ip4;
		dstAddress.m_ip4.m_address.m_i32 = ipHdr.m_dstAddress;
		break;

	case io.EthernetType.Ip6:
		if (size < sizeof (io.Ip6Hdr))
			return false;

		io.Ip6Hdr const* ip6Hdr = (io.Ip6Hdr const*) p;

		if (ip6Hdr.m_nextHeader != io.Protocol.Tcp ||
			ip6Hdr.m_payloadLength + sizeof (io.Ip6Hdr) > size)
			return false;

		p += sizeof (io.Ip6Hdr);
		size = ip6Hdr.m_payloadLength;

		srcAddress.m_family = io.AddressFamily.Ip6;
		srcAddress.m_ip6.m_address = *(io.Address_ip6 const*) ip6Hdr.m_srcAddress;
		dstAddress.m_family = io.AddressFamily.Ip6;
		dstAddress.m_ip6.m_address = *(io.Address_ip6 const*) ip6Hdr.m_dstAddress;
		break;

	default:
		return false;
	}

	if (size < sizeof (io.TcpHdr))
		return false;

	io.TcpHdr const* tcpHdr = (io.TcpHdr const*) p;
	size_t tcpHdrSize = tcpHdr.m_dataOffset * 4;
	if (tcpHdrSize < sizeof (io.TcpHdr) || tcpHdrSize > size)
		return false;

	p += tcpHdrSize;
	size -= tcpHdrSize;

	srcAddress.m_ip4.m_port = tcpHdr.m_srcPort;
	dstAddress.m_ip4.m_port = tcpHdr.m_dstPort;

	if (!(m_state & State.TcpConnection))
	{
		if (!(tcpHdr.m_flags & io.TcpFlags.Syn) && !size)
			return false;

		size_t seqNumberDelta = (tcpHdr.m_flags & io.TcpFlags.Syn) ? 1 : 0;
		if (srcAddress.isMatch (&m_filterAddress))
		{
			m_tcpPeer1.m_address = srcAddress;
			m_tcpPeer1.m_tcpSequencer.reset (tcpHdr.m_seqNumber + seqNumberDelta);
			m_tcpPeer2.m_address = dstAddress;
			m_tcpPeer2.m_tcpSequencer.reset (tcpHdr.m_ackNumber);
		}
		else if (dstAddress.isMatch (&m_filterAddress))
		{
			m_tcpPeer1.m_address = dstAddress;
			m_tcpPeer1.m_tcpSequencer.reset (tcpHdr.m_ackNumber);
			m_tcpPeer2.m_address = srcAddress;
			m_tcpPeer2.m_tcpSequencer.reset (tcpHdr.m_seqNumber + seqNumberDelta);
		}
		else
		{
			return false;
		}

		char const* params = $"$(srcAddress; B)$(dstAddress; B)";

		if (!(tcpHdr.m_flags & io.TcpFlags.Syn))
		{
			writeLog (
				TcpFlowMonLogRecordCode.ConnectionPickedUp,
				params,
				dynamic sizeof (params)
				);
		}
		else if (!(tcpHdr.m_flags & io.TcpFlags.Ack))
		{
			writeLog (
				TcpFlowMonLogRecordCode.Connecting,
				params,
				dynamic sizeof (params)
				);

			m_state |= State.TcpSyn;
		}
		else // Syn + Ack
		{
			writeLog (
				TcpFlowMonLogRecordCode.Connecting,
				params,
				dynamic sizeof (params)
				);

			writeLog (TcpFlowMonLogRecordCode.ConnectCompleted);
		}

		m_state |= State.TcpConnection;
	}

	if (
		m_tcpPeer1.m_address.isEqual (&srcAddress) &&
		m_tcpPeer2.m_address.isEqual (&dstAddress))
	{
		decodeTcpPacket (&m_tcpPeer1, tcpHdr, p, size);
	}
	else if (
		m_tcpPeer2.m_address.isEqual (&srcAddress) &&
		m_tcpPeer1.m_address.isEqual (&dstAddress))
	{
		decodeTcpPacket (&m_tcpPeer2, tcpHdr, p, size);
	}
	else
	{
		return false;
	}

	return true;
}

TcpFlowMonSession.decodeTcpPacket (
	TcpFlowMonSession.TcpPeer* peer,
	io.TcpHdr const* tcpHdr,
	void const* p,
	size_t size
	)
{
	if ((tcpHdr.m_flags & io.TcpFlags.Rst) ||
		(tcpHdr.m_flags & io.TcpFlags.Fin))
	{
		writeLog (
			TcpFlowMonLogRecordCode.Disconnected,
			peer.m_address,
			sizeof (peer.m_address)
			);

		m_state &= ~(State.TcpConnection | State.TcpSyn);
	}
	else if (
		(tcpHdr.m_flags & io.TcpFlags.Syn) &&
		(tcpHdr.m_flags & io.TcpFlags.Ack) &&
		m_state & State.TcpSyn)
	{
		writeLog (TcpFlowMonLogRecordCode.ConnectCompleted);
		peer.m_tcpSequencer.reset (tcpHdr.m_seqNumber + 1);
		m_state &= ~State.TcpSyn;
	}
	else if (size)
	{
		peer.m_tcpSequencer.write (tcpHdr.m_seqNumber, p, size);
	}
}

reactor TcpFlowMonSession.m_uiReactor ()
{
	m_title = $"TCP Mon $(m_filterCombo.m_currentText)";
	m_actionTable [ActionId.Capture].m_text = m_state ? "Stop Capture" : "Capture";
	m_actionTable [ActionId.Capture].m_icon = m_iconTable [m_state ? IconId.StopCapture : IconId.Capture];
	m_statusPaneTable [StatusPaneId.State].m_text =
		(m_state & State.Capturing) ? "Capturing" :
		(m_state & State.Resolving) ? "Resolving" :
		"Closed";
}

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