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/ipv4/Ipv4PacketParser.lvt

namespace Livt.Net

/**
 * Parses fixed, unfragmented IPv4 headers and bounds payload by total length.
 * Options and fragmentation are Unsupported. Success means declared bytes are
 * captured; checksum verification is a separate explicit query.
 * Bind a protocol-relative provider. Invalidate children before this parser, then
 * release the source. Metadata is cached only for the current parse lifetime.
 */
component Ipv4PacketParser<S: IPacketData> : IPacketData
{
	public const HEADER_LENGTH: int = 20

	// Owned header snapshot and payload view borrowing the constructor source.
	header: PacketHeader<S, HEADER_LENGTH>

	// Validation state for the current parse lifetime.
	status: PacketParseResult

	/**
	 * Binds the protocol input; construction performs no reads.
	 */
	new(source: S)
	{
		header = new PacketHeader<S, HEADER_LENGTH>(source)
	}

	/**
	 * Discards validated metadata and closes the borrowed payload view.
	 */
	public fn Invalidate()
	{
		status = PacketParseResult.NotReady
		header.Invalidate()
	}

	/**
	 * Parses once and replaces prior state even on failure. Checks structure,
	 * not endpoint policy or transport checksums.
	 */
	public fn TryParse() PacketParseResult
	{
		status = header.TryLoad()

		if (status != PacketParseResult.Success) {
			return status
		}

		var versionAndLength = header.GetByte(0)
		var version = ((versionAndLength as uint) >> 4) as int
		var ihl = ((versionAndLength as uint) & (0x0F as uint)) as int
		var total = header.GetWord(2)

		if (version != 4) {
			status = PacketParseResult.Unsupported
			return status
		}

		if (ihl < 5 || total < (ihl << 2)) {
			status = PacketParseResult.Invalid
			return status
		}

		var fragment = header.GetWord(6)

		if (ihl != 5 || (fragment != 0 && fragment != 16384)) {
			status = PacketParseResult.Unsupported
			return status
		}

		var captured = header.GetCapturedLength()
		var bounded = total

		if (bounded > captured) {
			bounded = captured
		}

		status = header.TryOpenPayload(HEADER_LENGTH, bounded - HEADER_LENGTH)

		if (status == PacketParseResult.Success && captured < total) {
			status = PacketParseResult.HeaderOnly
		}

		return status
	}

	/**
	 * Returns current validation state; unavailable sources invalidate access.
	 */
	public fn GetStatus() PacketParseResult
	{
		if ((status == PacketParseResult.Success || status == PacketParseResult.HeaderOnly) && !header.IsReady()) {
			return PacketParseResult.NotReady
		}

		return status
	}

	/**
	 * Reports available supported metadata, including partial captures.
	 */
	override fn IsReady() bool
	{
		return (status == PacketParseResult.Success || status == PacketParseResult.HeaderOnly) && header.IsReady()
	}

	/**
	 * Returns bounded captured payload bytes, not declared or wire length.
	 */
	override fn GetAvailableLength() int
	{
		if (!this.IsReady()) {
			return 0
		}

		return header.GetAvailableLength()
	}

	/**
	 * Reads the borrowed payload relative to this protocol's header.
	 */
	override fn TryRead(index: int, value: out byte) PacketDataResult
	{
		value = 0x00

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

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

	/**
	 * Returns TotalLength from the supported cached header; unavailable metadata returns 0.
	 */
	public fn GetTotalLength() int
	{
		if (!this.IsReady()) {
			return 0
		}

		return header.GetWord(2)
	}

	/**
	 * Returns Identification from the supported cached header; unavailable metadata returns 0.
	 */
	public fn GetIdentification() int
	{
		if (!this.IsReady()) {
			return 0
		}

		return header.GetWord(4)
	}

	/**
	 * Returns Protocol from the supported cached header; unavailable metadata returns 0.
	 */
	public fn GetProtocol() byte
	{
		if (!this.IsReady()) {
			return 0
		}

		return header.GetByte(9)
	}

	/**
	 * Returns DeclaredPayloadLength from the supported cached header; unavailable metadata returns 0.
	 */
	public fn GetDeclaredPayloadLength() int
	{
		if (!this.IsReady()) {
			return 0
		}

		return header.GetWord(2) - HEADER_LENGTH
	}

	/**
	 * Copies the cached SourceIpv4 value; unavailable metadata returns zero bytes.
	 */
	public fn GetSourceIpv4() byte[4]
	{
		if (!this.IsReady()) { return [0x00, 0x00, 0x00, 0x00] }

		var word0 = header.GetWord(12)
		var word1 = header.GetWord(14)
		return [NetworkOrder.HighByte(word0), NetworkOrder.LowByte(word0), NetworkOrder.HighByte(word1), NetworkOrder.LowByte(word1)]
	}

	/**
	 * Copies the cached DestinationIpv4 value; unavailable metadata returns zero bytes.
	 */
	public fn GetDestinationIpv4() byte[4]
	{
		if (!this.IsReady()) { return [0x00, 0x00, 0x00, 0x00] }

		var word0 = header.GetWord(16)
		var word1 = header.GetWord(18)
		return [NetworkOrder.HighByte(word0), NetworkOrder.LowByte(word0), NetworkOrder.HighByte(word1), NetworkOrder.LowByte(word1)]
	}

	/**
	 * Checks the cached IPv4 header checksum only. Parsing does not enforce it,
	 * preserving the endpoint's existing unchecked receive-checksum policy.
	 */
	public fn IsHeaderChecksumValid() bool
	{
		if (!this.IsReady()) {
			return false
		}

		var sum: int = 0

		for (var i = 0; i < HEADER_LENGTH; i = i + 2) {
			sum = sum + header.GetWord(i)
		}

		sum = ChecksumArithmetic.Fold(sum)
		sum = ChecksumArithmetic.Fold(sum)
		return sum == 65535
	}
}