Livt packages

Livt.Net

Version 1.4.1

Ethernet, IPv4, TCP, UDP, ARP, TFTP, and Telnet networking components for Livt hardware.

Published files (163)

src/tcp/TcpRetryTimer.lvt

namespace Livt.Net

/**
 * Millisecond RFC 6298 estimator, Karn sampling and exponential timeout backoff. Caller supplies
 * monotonic uint milliseconds (wrapping subtraction), one owner.
 */
component TcpRetryTimer<SESSIONS: int = 1>
{
	static assert SESSIONS > 0 && SESSIONS <= 16, "TCP sessions must be 1..16"

	// Selected timer; all timers share one serialized owner.
	session: int

	// Initial and minimum retransmission timeout in milliseconds.
	const INITIAL_TIMEOUT_MS: uint = 1000

	// Maximum backed-off retransmission timeout in milliseconds.
	const MAX_TIMEOUT_MS: uint = 60000

	// Smoothed RTT, variation and current timeout, all in milliseconds.
	srtt: uint[SESSIONS]
	variance: uint[SESSIONS]
	rto: uint[SESSIONS]

	// Active transmission timestamp and RTT-sampling eligibility.
	started: uint[SESSIONS]
	active: bool[SESSIONS]
	sampled: bool[SESSIONS]
	repeated: bool[SESSIONS]

	/**
	 * Creates an inactive timer with a one-second timeout in the inherited context.
	 */
	new()
	{
		for (var i = 0; i < SESSIONS; i++) {
			this.rto[i] = INITIAL_TIMEOUT_MS
		}
	}

	/**
	 * Selects a timer without changing its state. Requires index in 0..SESSIONS-1.
	 */
	public fn SelectSession(index: int)
	{
		// A single session has no runtime slot selection hardware.
		static if (SESSIONS == 1) {
			this.session = 0
		} else {
			this.session = index
		}
	}

	/**
	 * Discards RTT history and active timing, restoring the initial timeout.
	 */
	public fn Reset()
	{
		this.rto[this.session] = INITIAL_TIMEOUT_MS
		this.active[this.session] = false
		this.sampled[this.session] = false
		this.repeated[this.session] = false
	}

	/**
	 * Starts timing an outstanding transmission at wrapping unsigned millisecond time now. Keeps the
	 * current RTT estimate and timeout.
	 */
	public fn Start(now: uint)
	{
		this.started[this.session] = now
		this.active[this.session] = true
		this.repeated[this.session] = false
	}

	/**
	 * Reports whether an active transmission has reached its timeout at now, in wrapping
	 * milliseconds.
	 */
	public fn IsExpired(now: uint) bool
	{
		return this.active[this.session] && now - this.started[this.session] >= this.rto[this.session]
	}

	/**
	 * Records a retransmission at now and doubles its timeout up to MAX_TIMEOUT_MS. Call only for an
	 * active outstanding transmission; its RTT sample is excluded.
	 */
	public fn Retry(now: uint)
	{
		this.repeated[this.session] = true
		this.started[this.session] = now

		if (this.rto[this.session] < MAX_TIMEOUT_MS) {
			this.rto[this.session] = this.rto[this.session] << 1
		}

		if (this.rto[this.session] > MAX_TIMEOUT_MS) {
			this.rto[this.session] = MAX_TIMEOUT_MS
		}
	}

	/**
	 * Ends the active timing interval. Updates RTT estimates only for a transmission that was not
	 * retried; now is wrapping millisecond time.
	 */
	public fn Acknowledge(now: uint)
	{
		if (this.active[this.session] && !this.repeated[this.session]) {
			var elapsed = now - this.started[this.session]

			if (elapsed == 0) {
				elapsed = 1
			}

			if (!this.sampled[this.session]) {
				this.srtt[this.session] = elapsed
				this.variance[this.session] = elapsed >> 1
				this.sampled[this.session] = true
			} else {
				var difference: uint = 0

				if (this.srtt[this.session] > elapsed) {
					difference = this.srtt[this.session] - elapsed
				} else {
					difference = elapsed - this.srtt[this.session]
				}

				this.variance[this.session] = this.variance[this.session] - (this.variance[this.session] >> 2) + (difference >> 2)
				this.srtt[this.session] = this.srtt[this.session] - (this.srtt[this.session] >> 3) + (elapsed >> 3)
			}

			var spread = this.variance[this.session] << 2

			if (spread == 0) {
				spread = 1
			}

			this.rto[this.session] = this.srtt[this.session] + spread

			if (this.rto[this.session] < INITIAL_TIMEOUT_MS) {
				this.rto[this.session] = INITIAL_TIMEOUT_MS
			}

			if (this.rto[this.session] > MAX_TIMEOUT_MS) {
				this.rto[this.session] = MAX_TIMEOUT_MS
			}
		}

		this.active[this.session] = false
	}

	/**
	 * Raises the timeout floor for the next transmission without starting it. Caller supplies
	 * milliseconds no greater than MAX_TIMEOUT_MS.
	 */
	public fn RequireMinimum(value: uint)
	{
		if (this.rto[this.session] < value) {
			this.rto[this.session] = value
		}
	}

	/**
	 * Returns the current retransmission timeout in milliseconds.
	 */
	public fn GetTimeout() uint
	{
		return this.rto[this.session]
	}
}