Livt packages

Livt.Usb

Version 0.0.5

USB 2.0 low/full-speed PHY, host transfers, device discovery, HID keyboard, and read-only mass storage for Livt.

Published files (57)

src/descriptors/ConfigurationDescriptorParser.lvt

namespace Livt.Usb.Descriptors

using Livt.Usb
using Livt.IO

/**
 * Streaming bounded configuration parser, independent of HID or other class policy.
 * Keeps alternate zero for up to four interfaces and three nonzero endpoints.
 * Unknown descriptor types are skipped by length. Invalid lengths, duplicate endpoint
 * addresses, truncated descriptors and unsupported geometry fail closed.
 */
component ConfigurationDescriptorParser<LIMIT: int = 512,
	B: IRam<byte> & new() = SequentialRam<byte, 32, MemoryStyle.Block>>
{
	static assert LIMIT >= 9 && LIMIT <= 65535, "Configuration bound must be 9..65535"

	// The stream count includes LIMIT; untrusted total length retains all 16 bits.
	const COUNT_BITS: int = Bits.RequiredFor(LIMIT)

	// Current descriptor header and bounded stream counters.
	header: byte[9]
	offset: byte
	length: byte
	descriptorType: byte
	consumed: logic[COUNT_BITS]
	total: logic[16]
	invalid: bool
	configuration: byte
	declaredInterfaces: byte
	interfaces: byte
	endpoints: byte
	currentInterface: byte
	alternateZero: bool
	expectedEndpoints: byte
	seenEndpoints: byte

	// Small duplicate-detection arrays and one exclusively accessed endpoint table.
	// Eight bytes per endpoint: attributes, MPS low/high, interval, number/class/sub/protocol.
	numbers: byte[4]
	addresses: byte[3]
	currentClass: byte
	currentSubclass: byte
	currentProtocol: byte
	table: B

	/**
	 * Owns 32 metadata bytes; only the completed endpoint prefix can be read.
	 * Calls must be serialized. Substitute a compatible IRam to select other storage.
	 */
	new()
	{
		table = new B()
	}

	/**
	 * Starts a new configuration; only initialized table prefixes can be queried.
	 */
	public fn Reset()
	{
		state {
			offset = 0;
			length = 0;
			consumed = 0;
			total = 0;
			invalid = false
			interfaces = 0;
			endpoints = 0;
			configuration = 0;
			declaredInterfaces = 0
			currentInterface = 255;
			alternateZero = false;
			expectedEndpoints = 0;
			seenEndpoints = 0
		}
	}

	/**
	 * Consumes one byte without allocating storage proportional to descriptor length.
	 */
	public fn Feed(value: byte)
	{
		if (invalid) { return }

		if ((consumed as uint) >= (LIMIT as uint) || (total != 0 && (consumed as uint) >= (total as uint))) {
			invalid = true;
			return
		}

		if (offset == 0) {
			length = value

			if (length < 2 || (consumed as uint) + (length as uint) > (LIMIT as uint) || (total != 0 && (consumed as uint) + (length as uint) > (total as uint))) {
				invalid = true;
				return
			}
		}

		if (offset < 9) { header[offset as int] = value }

		if (offset == 1) { descriptorType = value }

		offset++;
		consumed++

		if (offset == length) {
			FinishDescriptor();
			offset = 0
		}
	}

	/**
	 * Validates and commits one complete standard descriptor; skips unknown types.
	 */
	fn FinishDescriptor()
	{
		if (total == 0) {
			if (descriptorType != 2 || length != 9) {
				invalid = true;
				return
			}

			total = (header[2] as logic[16]) | ((header[3] as logic[16]) << 8)
			declaredInterfaces = header[4]
			configuration = header[5]

			if (total < 9 || (total as uint) > (LIMIT as uint) || configuration == 0 || declaredInterfaces < 1 || declaredInterfaces > 4) { invalid = true }

			return
		}

		if (descriptorType == 2 || descriptorType == 1) {
			invalid = true;
			return
		}

		if (descriptorType == 4) {
			if (length != 9 || (alternateZero && seenEndpoints != expectedEndpoints)) {
				invalid = true;
				return
			}

			alternateZero = header[3] == 0

			if (!alternateZero) { return }

			if (interfaces == 4 || header[5] == 9) {
				invalid = true;
				return
			}

			for (var index: byte = 0; index < interfaces; index++) {
				if (numbers[index as int] == header[2]) {
					invalid = true;
					return
				}
			}

			currentInterface = interfaces
			numbers[interfaces as int] = header[2]
			currentClass = header[5]
			currentSubclass = header[6]
			currentProtocol = header[7]
			expectedEndpoints = header[4]
			seenEndpoints = 0
			interfaces++
		} elif (descriptorType == 5) {
			if (currentInterface == 255 || length < 7) {
				invalid = true;
				return
			}

			if (!alternateZero) { return }

			if (endpoints == 3 || (header[2] & 15) == 0 || (header[2] & 112) != 0) {
				invalid = true;
				return
			}

			for (var index: byte = 0; index < endpoints; index++) {
				if (addresses[index as int] == header[2]) {
					invalid = true;
					return
				}
			}

			addresses[endpoints as int] = header[2]

			for (var field: byte = 0; field < 8; field++) {
				var value: byte

				match field
				{
					0 =>
					{
						value = header[3] & 3
					}
					1 =>
					{
						value = header[4]
					}
					2 =>
					{
						value = header[5]
					}
					3 =>
					{
						value = header[6]
					}
					4 =>
					{
						value = numbers[currentInterface as int]
					}
					5 =>
					{
						value = currentClass
					}
					6 =>
					{
						value = currentSubclass
					}
					_ =>
					{
						value = currentProtocol
					}
				}

				table.Write((endpoints as int) * 8 + (field as int), value)
			}

			endpoints++;
			seenEndpoints++
		}
	}

	/**
	 * Exposes the header length after nine valid bytes for a bounded second request.
	 */
	public fn GetTotalLength() int
	{
		if (invalid || consumed < 9) { return 0 }

		return (total as uint) as int
	}

	/**
	 * Requires exact stream length and matching interface/endpoint declarations.
	 */
	public fn IsValid(speed: UsbSpeed) bool
	{
		if (invalid || (consumed as uint) != (total as uint) || total == 0 || offset != 0 || interfaces != declaredInterfaces
			|| (alternateZero && seenEndpoints != expectedEndpoints)) { return false }

		for (var index: byte = 0; index < endpoints; index++) {
			var row = (index as int) * 8
			var low = table.Read(row + 1)
			var high = table.Read(row + 2)
			var size = (low as logic[16]) | ((high as logic[16]) << 8)
			var attributes = table.Read(row)
			var interval = table.Read(row + 3)

			if (size < 1 || size > 64 || (speed == UsbSpeed.Low && size > 8)) { return false }

			if (attributes == 0 || attributes == 1) { return false }

			if (attributes == 2 && (speed != UsbSpeed.Full || (size != 8 && size != 16 && size != 32 && size != 64))) { return false }

			if (attributes == 3 && interval == 0) { return false }
		}

		return true
	}

	/**
	 * Returns the configuration value after complete validation.
	 */
	public fn GetConfiguration() int
	{
		return configuration as int
	}

	/**
	 * Returns the initialized endpoint count after complete validation.
	 */
	public fn GetEndpointCount() int
	{
		return endpoints as int
	}

	/**
	 * Finds an endpoint's interface class tuple without imposing class selection.
	 * Fields: 0 address, 1 transfer type, 2 packet bytes, 3 interval frames,
	 * 4 interface number, 5 class, 6 subclass, 7 protocol. Invalid queries return -1.
	 */
	public fn GetEndpoint(index: int, field: int) int
	{
		if (index < 0 || index >= (endpoints as int)) { return -1 }

		if (field == 0) { return addresses[index] as int }

		if (field < 1 || field > 7) { return -1 }

		var row = index * 8

		if (field == 2) {
			var low = table.Read(row + 1)
			var high = table.Read(row + 2)
			return (low as int) | ((high as int) << 8)
		}

		if (field == 1) { return table.Read(row) as int }

		return table.Read(row + field) as int
	}
}