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

import "std_List.jnc"
import "log_Writer.jnc"

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

class TcpSequencer {
	enum Defaults {
		OutOfSeqLimit = 8
	}

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

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

public:
	void write(
		uint64_t timestamp,
		uint32_t seqNumber,
		void const* p,
		size_t size
	);

	void flush(uint64_t timestamp) {
		flushOutOfSeqSegments(timestamp, 0);
	}

	void finalize(
		uint64_t timestamp,
		uint32_t seqNumber
	);

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

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

	void flushOutOfSeqSegments(
		uint64_t timestamp,
		size_t limit
	);

	void writeAlignedOutOfSeqSegments(uint64_t timestamp);
}

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

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

void TcpSequencer.finalize(
	uint64_t timestamp,
	uint32_t seqNumber
) {
	flushOutOfSeqSegments(timestamp, 0);

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

void TcpSequencer.writeInSeqSegment(
	uint64_t timestamp,
	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;
	}

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

void TcpSequencer.addOutOfSeqSegment(
	uint64_t timestamp,
	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(timestamp, m_outOfSeqLimit);
}

void TcpSequencer.flushOutOfSeqSegments(
	uint64_t timestamp,
	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;
		g_logWriter.write(timestamp, TcpFlowLogRecordCode.DataLost, &lostSize, sizeof(lostSize));
		writeInSeqSegment(timestamp, segment.m_seqNumber, segment.m_p, segment.m_size);
		writeAlignedOutOfSeqSegments(timestamp);
	}
}

void TcpSequencer.writeAlignedOutOfSeqSegments(uint64_t timestamp) {
	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(timestamp, segment.m_seqNumber, segment.m_p, segment.m_size);
	}
}

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