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/packet/PacketHeader.lvt

namespace Livt.Net

/**
 * Internal fixed-header cache and bounded payload view.
 * The owner invalidates descendants before reloading or releasing the source.
 * Only the header is copied; payload reads remain scheduled source operations.
 */
component PacketHeader<S: IPacketData, SIZE: int> : IPacketData
{
	// Borrowed provider; reads and payload views share its publication lifetime.
	source: S

	// Owned header snapshot and validation state.
	const HEADER_BITS: int = SIZE * 8
	const LOAD_BITS: int = Bits.RequiredFor(SIZE)
	header: logic[HEADER_BITS]
	loaded: bool
	opened: bool

	// Opened payload bounds relative to the borrowed provider.
	payloadOffset: int
	payloadLength: int

	/**
	 * Binds stable packet storage.
	 */
	new(source: S)
	{
		this.source = source
	}

	/**
	 * Invalidates both the cache and its payload view before source reuse.
	 */
	public fn Invalidate()
	{
		loaded = false
		opened = false
		payloadLength = 0
	}

	/**
	 * Reads the fixed header once; any failure discards the prior cache.
	 */
	public fn TryLoad() PacketParseResult
	{
		this.Invalidate()

		if (!source.IsReady()) {
			return PacketParseResult.NotReady
		}

		var captured = source.GetAvailableLength()

		if (captured < SIZE) {
			return PacketParseResult.Truncated
		}

		for (var i: logic[LOAD_BITS] = 0; ((i as uint) as int) < SIZE; i++) {
			var value: byte = 0x00

			var result = source.TryRead((i as uint) as int, value)

			if (result != PacketDataResult.Success) {
				return PacketParseResult.NotReady
			}

			// A constant shift appends one byte without an indexed array write.
			header = (header << 8) | ((value as uint) as logic[HEADER_BITS])
		}

		loaded = true
		return PacketParseResult.Success
	}

	/**
	 * Returns source capture size, never a wire-length claim.
	 */
	public fn GetCapturedLength() int
	{
		return source.GetAvailableLength()
	}

	/**
	 * Reads a cached octet for internal protocol decoding.
	 */
	public fn GetByte(index: int) byte
	{
		if (!loaded || !source.IsReady() || index < 0 || index >= SIZE) {
			return 0x00
		}

		return this.ByteAt(header, index)
	}

	/**
	 * Decodes an unsigned network-order word from the cache.
	 */
	public fn GetWord(index: int) int
	{
		var high = this.GetByte(index)
		var low = this.GetByte(index + 1)
		return NetworkOrder.Word(high, low)
	}

	/**
	 * Decodes a network-order sequence number without signed overflow.
	 */
	public fn GetSequence(index: int) uint
	{
		var high = this.GetWord(index)
		var low = this.GetWord(index + 2)
		return ((high as uint) << 16) | (low as uint)
	}

	/**
	 * Opens only a range contained in the captured prefix.
	 */
	public fn TryOpenPayload(offset: int, length: int) PacketParseResult
	{
		opened = false
		var available = source.GetAvailableLength()

		if (!loaded || !source.IsReady()) {
			return PacketParseResult.NotReady
		}

		if (offset < SIZE || offset > available || length < 0 || length > available - offset) {
			return PacketParseResult.Invalid
		}

		payloadOffset = offset
		payloadLength = length
		opened = true
		return PacketParseResult.Success
	}

	/**
	 * Reports whether validated payload bounds are available.
	 */
	override fn IsReady() bool
	{
		return opened && source.IsReady()
	}

	/**
	 * Returns the captured payload length, excluding enclosing padding.
	 */
	override fn GetAvailableLength() int
	{
		if (!this.IsReady()) {
			return 0
		}

		return payloadLength
	}

	/**
	 * Reads a payload-relative byte without copying payload storage.
	 */
	override fn TryRead(index: int, value: out byte) PacketDataResult
	{
		value = 0x00

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

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

		var readValue: byte = 0x00
		var result = source.TryRead(payloadOffset + index, readValue)
		value = readValue
		return result
	}

	/** Pure selection from a fully loaded network-order shift register. */
	inline fn ByteAt[](snapshot: logic[HEADER_BITS], index: int) byte
	{
		if (index < 0 || index >= SIZE) { return 0x00 }
		return (snapshot >> ((SIZE - 1 - index) << 3)) as byte
	}

}