//..............................................................................
//
// TCP sequencer ensures handling of TCP segments in correct order
//

import "std_List.jnc"

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

class TcpSequencer
{
	enum Defaults
	{
		OutOfSeqLimit = 8
	}

protected:
	struct OutOfSeqSegment
	{
		OutOfSeqSegment* m_next;
		uint32_t m_seqNumber;
		void* m_p;
		size_t m_size;
	}

	log.Writer*	m_logWriter;

public:
	std.List readonly m_outOfSeqList;
	size_t readonly m_totalLostSize;
	size_t m_outOfSeqLimit;
	uint32_t m_seqNumber;
	uint64_t m_logRecordCode;

public:
	construct(doc.PluginHost* pluginHost);

	write(
		uint32_t seqNumber,
		void const* p,
		size_t size
		);

	flush()
	{
		flushOutOfSeqSegments(0);
	}

	finalize(uint32_t seqNumber);

protected:
	writeInSeqSegment(
		uint32_t seqNumber,
		void const* p,
		size_t size
		);

	addOutOfSeqSegment(
		uint32_t seqNumber,
		void const* p,
		size_t size
		);

	flushOutOfSeqSegments(size_t limit);
	writeAlignedOutOfSeqSegments();
}

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

TcpSequencer.construct(doc.PluginHost* pluginHost)
{
	m_logWriter = pluginHost.m_log.m_writer;
	m_outOfSeqLimit = Defaults.OutOfSeqLimit;
}

TcpSequencer.write(
	uint32_t seqNumber,
	void const* p,
	size_t size
	)
{
	if ((int32_t)(seqNumber - m_seqNumber) > 0)
	{
		addOutOfSeqSegment(seqNumber, p, size);
	}
	else
	{
		writeInSeqSegment(seqNumber, p, size);
		writeAlignedOutOfSeqSegments();
	}
}

TcpSequencer.finalize(uint32_t seqNumber)
{
	flushOutOfSeqSegments(0);

	if (seqNumber > m_seqNumber)
	{
		size_t lostSize = seqNumber - m_seqNumber;
		m_logWriter.write(TcpFlowMonLogRecordCode.DataLost, &lostSize, sizeof(lostSize));
		m_totalLostSize += lostSize;
	}
}

TcpSequencer.writeInSeqSegment(
	uint32_t seqNumber,
	void const* p,
	size_t size
	)
{
	uint32_t newSeqNumber = seqNumber + size;
	if ((int32_t)(newSeqNumber - m_seqNumber) <= 0) // retransmission
		return;

	if ((int32_t)(seqNumber - m_seqNumber) < 0) // partial retransmission
	{
		size_t skip = m_seqNumber - seqNumber;
		p += skip;
		size -= skip;
	}

	m_logWriter.write(m_logRecordCode, p, size);
	m_seqNumber = newSeqNumber;
}

TcpSequencer.addOutOfSeqSegment(
	uint32_t seqNumber,
	void const* p,
	size_t size
	)
{
	// find location to insert it

	std.ListEntry* it = m_outOfSeqList.m_tail;
	for (; it; it = it.m_prev)
	{
		OutOfSeqSegment* segment = it.m_data;
		if ((int32_t)(seqNumber - segment.m_seqNumber) >= 0)
			break;
	}

	OutOfSeqSegment* segment = new OutOfSeqSegment;
	segment.m_seqNumber = seqNumber;
	segment.m_p = new char[size];
	segment.m_size = size;
	memcpy(segment.m_p, p, size);
	m_outOfSeqList.insertAfter(segment, it);

	// maintain out-of-seq limit

	flushOutOfSeqSegments(m_outOfSeqLimit);
}

TcpSequencer.flushOutOfSeqSegments(size_t limit)
{
	while (m_outOfSeqList.m_count > limit)
	{
		OutOfSeqSegment* segment = m_outOfSeqList.removeHead();
		uint32_t lostSize = segment.m_seqNumber - m_seqNumber;
		m_totalLostSize += lostSize;
		m_logWriter.write(TcpFlowMonLogRecordCode.DataLost, &lostSize, sizeof(lostSize));
		writeInSeqSegment(segment.m_seqNumber, segment.m_p, segment.m_size);
		writeAlignedOutOfSeqSegments();
	}
}

TcpSequencer.writeAlignedOutOfSeqSegments()
{
	while (m_outOfSeqList.m_count)
	{
		OutOfSeqSegment* segment = m_outOfSeqList.m_head.m_data;
		if ((int32_t)(segment.m_seqNumber - m_seqNumber) > 0)
			break;

		m_outOfSeqList.removeHead();
		writeInSeqSegment(segment.m_seqNumber, segment.m_p, segment.m_size);
	}
}

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