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/tftp/TftpRequest.lvt

namespace Livt.Net

using Livt.IO

/**
 * Bounded RFC 1350 packet decoder. Owns a 516-byte snapshot so the network
 * source may be released after Load. One serialized caller in the same context.
 * STYLE selects the default RAM preference. B may replace storage and must provide
 * at least 516 bytes. Only successfully loaded bytes are readable.
 * Supports octet WRQ filenames up to 63 bytes and baseline 512-byte DATA.
 */
component TftpRequest<P: IPacketData, STYLE: MemoryStyle = MemoryStyle.Block,
	B: IRam<byte> & new() = Ram<byte, 516, STYLE>>
{
	// Borrowed published datagram and owned validated packet prefix.
	source: P
	bytes: B
	length: int

	/**
	 * Binds a stable published UDP payload; Load performs the snapshot.
	 */
	new(source: P)
	{
		this.source = source
		bytes = new B()
	}

	/**
	 * Copies a complete 2..516-byte datagram. Failure invalidates the previous snapshot.
	 */
	public fn Load() bool
	{
		length = 0
		var count = source.GetAvailableLength()

		if (!source.IsReady() || count < 2 || count > 516) {
			return false
		}

		for (var index = 0; index < count; index++) {
			var value: byte = 0

			if (source.TryRead(index, value) != PacketDataResult.Success) {
				return false
			}

			bytes.Write(index, value)
		}

		length = count
		return true
	}

	/**
	 * Returns the snapshot length, or zero after failed loading.
	 */
	public fn GetLength() int
	{
		return length
	}

	/**
	 * Returns one snapshot byte, or zero outside the valid prefix.
	 */
	public fn GetByte(index: int) byte
	{
		if (index < 0 || index >= length) {
			return 0x00
		}

		return bytes.Read(index)
	}

	/**
	 * Returns a network-order opcode, or zero without a complete opcode.
	 */
	public fn GetOpcode() int
	{
		if (length < 2) {
			return 0
		}

		return (GetByte(0) as int) * 256 + (GetByte(1) as int)
	}

	/**
	 * Returns a network-order block number; callers must require at least four bytes.
	 */
	public fn GetBlock() int
	{
		return (GetByte(2) as int) * 256 + (GetByte(3) as int)
	}

	/**
	 * Returns the filename length of a valid octet WRQ, or zero. Mode matching
	 * is case-insensitive. Complete option/value pairs are accepted but ignored,
	 * as permitted when the server does not negotiate RFC 2347 options.
	 */
	public fn GetWriteNameLength() int
	{
		if (GetOpcode() != 2) {
			return 0
		}

		var end = 2

		while (end < length && end < 66 && GetByte(end) != 0) {
			end++
		}

		var nameLength = end - 2

		if (nameLength < 1 || nameLength > 63 || end + 7 > length || GetByte(end) != 0) {
			return 0
		}

		if ((GetByte(end + 1) as int | 32) != 111
			|| (GetByte(end + 2) as int | 32) != 99
			|| (GetByte(end + 3) as int | 32) != 116
			|| (GetByte(end + 4) as int | 32) != 101
			|| (GetByte(end + 5) as int | 32) != 116 || GetByte(end + 6) != 0) {
			return 0
		}

		var cursor = end + 7
		var fields = 0

		while (cursor < length) {
			var start = cursor

			while (cursor < length && GetByte(cursor) != 0) {
				cursor++
			}

			if (cursor == start || cursor == length) {
				return 0
			}

			cursor++
			fields++
		}

		if (fields % 2 != 0) {
			return 0
		}

		return nameLength
	}
}