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/link/TestFrameTransmitter.lvt

namespace Livt.Net

enum TestTransmitState
{
	Idle
	Borrowed
	Terminal
	Resetting
}

/**
 * Deterministic transmitter for contract tests. Advance reads at most one source
 * byte. A later Advance publishes delivery, allowing tests to hold completion.
 * Test controls/capture access are outside the public transmitter capability.
 */
component TestFrameTransmitter<P: IPacketData, MAX_LENGTH: int = 1514, CANCELLABLE: bool = true> : IFrameTransmitter
{
	static assert MAX_LENGTH >= 60, "Frame limit must accommodate minimum Ethernet frame"

	// Borrowed fixed source; retained from acceptance until terminal quiescence.
	source: P

	// Owned capture snapshot and submitted length.
	captured: byte[MAX_LENGTH]
	const LENGTH_BITS: int = Bits.RequiredFor(MAX_LENGTH)
	capturedLength: logic[LENGTH_BITS]
	length: logic[LENGTH_BITS]

	// Transfer lifecycle, harness controls and retained terminal result.
	phase: TestTransmitState
	ready: bool
	borrowed: bool
	cancelRequested: bool
	failureRequested: bool
	outcome: FrameTransmitOutcome

	/**
	 * Borrows a fixed source, owns capture storage and starts idle and ready.
	 * Construction does not read or acquire the source.
	 */
	new(source: P)
	{
		this.source = source
		capturedLength = 0
		length = 0
		phase = TestTransmitState.Idle
		ready = true
		borrowed = false
		cancelRequested = false
		failureRequested = false
		outcome = FrameTransmitOutcome.DeviceFailure
	}

	/**
	 * Returns the minimum supported submission length in bytes, including padding and excluding FCS.
	 */
	override fn GetMinimumFrameLength() int
	{
		return 60
	}

	/**
	 * Returns the maximum supported submission length in bytes, excluding FCS.
	 */
	override fn GetMaximumFrameLength() int
	{
		return MAX_LENGTH
	}

	/**
	 * Reports whether cancellation can be requested; it does not guarantee immediate quiescence.
	 */
	override fn SupportsCancellation() bool
	{
		return CANCELLABLE
	}

	/**
	 * Reports whether the source and its dependencies must remain published and unchanged.
	 */
	override fn IsSourceBorrowed() bool
	{
		return borrowed
	}

	/**
	 * Requests transmission of the fixed, prepared source. Accepted starts the borrow,
	 * not delivery. Rejection leaves the source unborrowed and preserves old results.
	 * Busy means an active transfer or unacknowledged completion; NotReady means
	 * admission is paused. Invalid means unprepared/empty data; Unsupported means
	 * length outside the advertised bounds; DeviceFailure means admission failed.
	 */
	override fn TrySubmit() FrameLinkResult
	{
		if (phase == TestTransmitState.Resetting) {
			return FrameLinkResult.NotReady
		}

		if (phase != TestTransmitState.Idle) {
			return FrameLinkResult.Busy
		}

		if (!ready) {
			return FrameLinkResult.NotReady
		}

		if (failureRequested) {
			return FrameLinkResult.DeviceFailure
		}

		if (!source.IsReady()) {
			return FrameLinkResult.Invalid
		}

		var size = source.GetAvailableLength()

		if (size <= 0) {
			return FrameLinkResult.Invalid
		}

		if (size < 60 || size > MAX_LENGTH) {
			return FrameLinkResult.Unsupported
		}

		length = size as logic[LENGTH_BITS]
		capturedLength = 0
		cancelRequested = false
		borrowed = true
		phase = TestTransmitState.Borrowed
		return FrameLinkResult.Accepted
	}

	/**
	 * Requests cancellation of an active borrow. Accepted is not terminal completion;
	 * keep the source unchanged until completion or coordinated reset finishes.
	 * Unsupported preserves ownership; Busy means already requested, Invalid means
	 * no pending transfer, and NotReady means reset prevents cancellation.
	 */
	override fn TryCancel() FrameLinkResult
	{
		if (phase == TestTransmitState.Resetting) {
			return FrameLinkResult.NotReady
		}

		if (phase != TestTransmitState.Borrowed) {
			return FrameLinkResult.Invalid
		}

		if (!CANCELLABLE) {
			return FrameLinkResult.Unsupported
		}

		if (cancelRequested) {
			return FrameLinkResult.Busy
		}

		cancelRequested = true
		return FrameLinkResult.Accepted
	}

	/**
	 * Returns Success and a retained terminal outcome when available, otherwise
	 * NotReady. The output is meaningful only on Success. This does not acknowledge it.
	 */
	override fn TryGetCompletion(outcome: out FrameTransmitOutcome) FrameLinkResult
	{
		outcome = FrameTransmitOutcome.DeviceFailure

		if (phase != TestTransmitState.Terminal) {
			return FrameLinkResult.NotReady
		}

		outcome = this.outcome
		return FrameLinkResult.Success
	}

	/**
	 * Acknowledges a retained terminal result, allowing another submission.
	 * Returns NotReady when no terminal result is available.
	 */
	override fn TryAcknowledgeCompletion() FrameLinkResult
	{
		if (phase != TestTransmitState.Terminal) {
			return FrameLinkResult.NotReady
		}

		phase = TestTransmitState.Idle
		return FrameLinkResult.Success
	}

	/**
	 * Controls submission readiness and pauses byte transfer; terminal handling still advances.
	 */
	public fn SetReady(ready: bool)
	{
		this.ready = ready
	}

	/**
	 * Inject/clear device failure. During a borrow it is observed on Advance.
	 */
	public fn SetFailure(failed: bool)
	{
		failureRequested = failed
	}

	/**
	 * Advances an active transfer by at most one scheduled source read. Cancellation
	 * and errors take priority over paused transfer. A later call publishes delivery
	 * after the last byte; terminal completion releases the source borrow.
	 */
	public fn Advance()
	{
		if (phase != TestTransmitState.Borrowed) {
			return
		}

		// Quiescence/error handling continues even when data transfer is paused.

		if (failureRequested) {
			outcome = FrameTransmitOutcome.DeviceFailure;
			borrowed = false;
			phase = TestTransmitState.Terminal;
			return
		}

		if (cancelRequested) {
			outcome = FrameTransmitOutcome.Cancelled;
			borrowed = false;
			phase = TestTransmitState.Terminal;
			return
		}

		if (!ready) {
			return
		}

		if (capturedLength == length) {
			outcome = FrameTransmitOutcome.Delivered;
			borrowed = false;
			phase = TestTransmitState.Terminal;
			return
		}

		var value: byte = 0x00
		var result = source.TryRead((capturedLength as uint) as int, value)

		if (result != PacketDataResult.Success) {
			outcome = FrameTransmitOutcome.DeviceFailure;
			borrowed = false;
			phase = TestTransmitState.Terminal;
			return
		}

		captured[(capturedLength as uint) as int] = value
		capturedLength++
	}

	/**
	 * Returns the number of captured bytes, including a partial transfer snapshot.
	 */
	public fn GetCapturedLength() int
	{
		return (capturedLength as uint) as int
	}

	/**
	 * Reads index in 0..capturedLength-1; Invalid leaves output zero.
	 * Capture inspection does not acquire the source or acknowledge completion.
	 */
	public fn TryReadCaptured(index: int, value: out byte) PacketDataResult
	{
		value = 0x00

		if (index < 0 || index >= ((capturedLength as uint) as int)) {
			return PacketDataResult.Invalid
		}

		value = captured[index]
		return PacketDataResult.Success
	}

	/**
	 * Enters the reset barrier and disables readiness without releasing the source.
	 * Only CompleteReset after reader quiescence ends the borrow.
	 */
	public fn BeginReset()
	{
		phase = TestTransmitState.Resetting;
		ready = false
	}

	/**
	 * Ends the borrow only when the harness declares all readers quiescent.
	 */
	public fn CompleteReset()
	{
		if (phase != TestTransmitState.Resetting) {
			return
		}

		borrowed = false
		capturedLength = 0
		length = 0
		cancelRequested = false
		failureRequested = false
		outcome = FrameTransmitOutcome.DeviceFailure
		phase = TestTransmitState.Idle
		ready = false
	}
}