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/TcpSendBuffer.lvt

namespace Livt.Net

using Livt.IO

/**
 * Banked retained TCP payloads with a borrowed transmission view; serialize every call.
 * Owns B in the inherited clock/reset context. B requires a parameterless constructor and
 * at least CAPACITY * SESSIONS byte addresses. The default uses block RAM; callers may
 * substitute array, automatic, distributed or custom scheduled storage. Access has no
 * fixed-cycle guarantee. Length metadata guards unwritten bytes; reset discards all views
 * and lengths without requiring payload initialization or retention.
 */
component TcpSendBuffer<CAPACITY: int = 256, SESSIONS: int = 1,
	B: IRam<byte> & new() = BlockRam<byte, (CAPACITY * SESSIONS)>> : IPacketData
{
	static assert SESSIONS > 0 && SESSIONS <= 16, "TCP sessions must be 1..16"

	static assert CAPACITY > 0 && CAPACITY <= 536, "TCP send capacity must be 1..536"

	// Width of the retained or received byte count, including full capacity.
	const COUNT_BITS: int = Bits.RequiredFor(CAPACITY)

	// Owned payload storage and retained byte count; no payload initialization is required.
	storage: B
	length: logic[SESSIONS, COUNT_BITS]

	// Selected retained payload; selection is pinned while a view is open.
	session: int

	// Borrowed transmission window over retained bytes.
	offset: logic[COUNT_BITS]
	viewLength: logic[COUNT_BITS]
	opened: bool

	/**
	 * Creates owned storage and empty payload metadata in the inherited clock/reset context.
	 */
	new()
	{
		this.storage = new B()
	}

	/**
	 * Selects one retained payload when no view is published. Invalid indices or an open view
	 * return false without changing the selection. All calls share one serialized owner.
	 */
	public fn SelectSession(index: int) bool
	{
		if (this.opened || index < 0 || index >= SESSIONS) {
			return false
		}

		// A single session has no runtime slot selection hardware.
		static if (SESSIONS == 1) {
			this.session = 0
		} else {
			this.session = index
		}
		return true
	}

	/**
	 * Returns the retained byte count, including bytes outside the current transmission view.
	 */
	public fn GetLength() int
	{
		return (this.length[this.session] as uint) as int
	}

	/**
	 * Appends one byte when no view is open and capacity remains. Success means the RAM write
	 * completed; rejection leaves the payload unchanged.
	 */
	public fn TryAppend(value: byte) bool
	{
		if (this.opened || this.GetLength() == CAPACITY) {
			return false
		}

		this.storage.Write(this.session * CAPACITY + this.GetLength(), value)
		this.length[this.session] = ((this.length[this.session] as uint) + (1 as uint)) as logic[COUNT_BITS]
		return true
	}

	/**
	 * Discards retained length and any view without clearing RAM. Call only after all readers have
	 * released their borrow.
	 */
	public fn Clear()
	{
		this.opened = false
		this.length[this.session] = 0
	}

	/**
	 * Borrows the retained range [start, start + count) for transmission. Requires non-negative
	 * bounds within GetLength(); failure leaves the existing view unchanged.
	 */
	public fn Open(start: int, count: int) bool
	{
		if (this.opened || start < 0 || count < 0 || start > this.GetLength() || count > this.GetLength() - start) {
			return false
		}

		this.offset = start as logic[COUNT_BITS]
		this.viewLength = count as logic[COUNT_BITS]
		this.opened = true
		return true
	}

	/**
	 * Ends the transmission view without discarding retained bytes. The caller must first finish all
	 * reads.
	 */
	public fn Close()
	{
		this.opened = false
	}

	/**
	 * Reports whether a transmission view is open; this is a snapshot, not a reservation.
	 */
	override fn IsReady() bool
	{
		return this.opened
	}

	/**
	 * Returns the open view length, or zero when closed.
	 */
	override fn GetAvailableLength() int
	{
		if (!this.opened) {
			return 0
		}

		return (this.viewLength as uint) as int
	}

	/**
	 * Reads a view-relative byte through scheduled RAM access. Failure assigns zero and reports
	 * NotReady or Invalid; reads do not consume payload.
	 */
	override fn TryRead(index: int, value: out byte) PacketDataResult
	{
		value = 0

		if (!this.opened) {
			return PacketDataResult.NotReady
		}

		if (index < 0 || index >= this.GetAvailableLength()) {
			return PacketDataResult.Invalid
		}

		value = this.storage.Read(this.session * CAPACITY + ((this.offset as uint) as int) + index)
		return PacketDataResult.Success
	}
}