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/testing/TestHostController.lvt
namespace Livt.Usb.Testing
using Livt.IO
using Livt.Usb
using Livt.Usb.Host
/**
* Deterministic, manually advanced IHostController test double; no physical USB I/O.
* One test owner serializes calls, injects received bytes and advances frame deadlines.
* Accepted request snapshots and completion retention match the public contract.
* No autonomous process, enumeration, token handling or endpoint toggle simulation.
*/
component TestHostController<MODEL_TRANSFER_BYTES: int = 512, STYLE: MemoryStyle = MemoryStyle.Block,
B: IRam<byte> & new() = Ram<byte, MODEL_TRANSFER_BYTES, STYLE>> : IHostController
{
static assert MODEL_TRANSFER_BYTES >= 8 && MODEL_TRANSFER_BYTES <= 65535, "Transfer capacity must be 8..65535 bytes"
// Owned policy/storage and connection state, separate from retained completion.
profile: HostProfile<MODEL_TRANSFER_BYTES>
storage: TransferStorage<MODEL_TRANSFER_BYTES, STYLE, B>
connected: bool
speed: UsbSpeed
transferState: TransferState
result: TransferResult
actualLength: int
framesRemaining: int
// Observable accepted request snapshot for tests; consumers use IHostController.
public submittedAddress: int
public submittedEndpoint: int
public submittedKind: TransferKind
public submittedDirection: TransferDirection
public submittedMaxPacketSize: int
public submittedLength: int
public submittedIntervalFrames: int
/**
* Creates a disconnected, idle controller with owned configurable storage.
*/
new()
{
profile = new HostProfile<MODEL_TRANSFER_BYTES>()
storage = new TransferStorage<MODEL_TRANSFER_BYTES, STYLE, B>()
}
/**
* Reports the initial profile's speed/type support independently of connection.
*/
override fn Supports(speed: UsbSpeed, kind: TransferKind) bool
{
return profile.Supports(speed, kind)
}
/**
* Returns one direct device; this model does not enumerate or allocate a table.
*/
override fn GetDeviceCapacity() int
{
return 1
}
/**
* Returns the initial profile's four interface entries.
*/
override fn GetInterfaceCapacity() int
{
return 4
}
/**
* Returns four endpoint entries, counting control endpoint zero once.
*/
override fn GetEndpointCapacity() int
{
return 4
}
/**
* Returns data-stage capacity excluding setup bytes.
*/
override fn GetTransferCapacity() int
{
return MODEL_TRANSFER_BYTES
}
/**
* Reports the manually established test connection.
*/
override fn IsConnected() bool
{
return connected
}
/**
* Returns speed or Unknown after disconnection/reset.
*/
override fn GetSpeed() UsbSpeed
{
return speed
}
/**
* Stages setup only while Idle; active/completed transfers retain their snapshot.
*/
override fn TryStageSetup(value: byte) bool
{
if (transferState != TransferState.Idle) {
return false
}
return storage.TryAppendSetup(value)
}
/**
* Stages output only while Idle, with no mutation on failure.
*/
override fn TryStageOutput(value: byte) bool
{
if (transferState != TransferState.Idle) {
return false
}
return storage.TryAppendPayload(value)
}
/**
* Discards rejected/prepared requests only when no transfer owns storage.
*/
override fn TryClearStaging() bool
{
if (transferState != TransferState.Idle) {
return false
}
storage.Clear()
return true
}
/**
* Validates and reserves one request. No completion occurs until a test hook acts.
* Rejections preserve staging and the previous accepted request snapshot.
*/
override fn TrySubmit(address: int, endpoint: int, kind: TransferKind,
direction: TransferDirection, maxPacketSize: int, length: int,
timeoutFrames: int, intervalFrames: int) SubmissionResult
{
if (transferState != TransferState.Idle) {
return SubmissionResult.Busy
}
if (!connected) {
return SubmissionResult.Disconnected
}
if (!profile.Supports(speed, kind)) {
return SubmissionResult.Unsupported
}
if (!profile.IsValid(speed, address, endpoint, kind, maxPacketSize, length, timeoutFrames, intervalFrames)
|| !storage.IsValid(kind, direction, length)) {
return SubmissionResult.Invalid
}
submittedAddress = address
submittedEndpoint = endpoint
submittedKind = kind
submittedDirection = direction
submittedMaxPacketSize = maxPacketSize
submittedLength = length
submittedIntervalFrames = intervalFrames
framesRemaining = timeoutFrames
actualLength = 0
result = TransferResult.Pending
transferState = TransferState.Active
return SubmissionResult.Accepted
}
/**
* Reports retained lifecycle state without advancing the simulated bus.
*/
override fn GetState() TransferState
{
return transferState
}
/**
* Reports the terminal result, or Pending while idle/active.
*/
override fn GetResult() TransferResult
{
return result
}
/**
* Returns the retained completion length, zero before completion.
*/
override fn GetActualLength() int
{
return actualLength
}
/**
* Reads only retained input completion bytes; failure writes zero.
*/
override fn TryReadInput(index: int, value: out byte) bool
{
value = 0
if (transferState != TransferState.Complete || submittedDirection != TransferDirection.DeviceToHost
|| index < 0 || index >= actualLength) {
return false
}
return storage.TryReadPayload(index, value)
}
/**
* Completes active work as Cancelled, preserving already injected input bytes.
*/
override fn Cancel() bool
{
if (transferState != TransferState.Active) {
return false
}
Finish(TransferResult.Cancelled, ReceivedLength())
return true
}
/**
* Releases completion and clears payload/setup metadata for the next request.
*/
override fn TryRelease() bool
{
if (transferState != TransferState.Complete) {
return false
}
storage.Clear()
transferState = TransferState.Idle
result = TransferResult.Pending
actualLength = 0
return true
}
/**
* Establishes a supported test connection only from disconnected, idle state.
*/
public fn Connect(speed: UsbSpeed) bool
{
if (connected || transferState != TransferState.Idle || !profile.Supports(speed, TransferKind.Control)) {
return false
}
this.speed = speed
connected = true
return true
}
/**
* Disconnects and completes active work; a previously completed result is retained.
*/
public fn Disconnect()
{
connected = false
speed = UsbSpeed.Unknown
if (transferState == TransferState.Active) {
Finish(TransferResult.Disconnected, ReceivedLength())
}
}
/**
* Injects one validated IN byte while active. Overflow terminates the request.
* Packet CRC, ACK/toggle behavior and packet boundaries are not modeled.
*/
public fn ReceiveByte(value: byte) bool
{
if (transferState != TransferState.Active || submittedDirection != TransferDirection.DeviceToHost) {
return false
}
if (storage.GetLength() >= submittedLength || !storage.TryAppendPayload(value)) {
Finish(TransferResult.Overflow, storage.GetLength())
return false
}
return true
}
/**
* Injects terminal outcome. IN length must equal injected bytes; successful OUT
* must acknowledge the full request. Pending/Disconnected/Cancelled use other hooks.
*/
public fn Complete(result: TransferResult, actualLength: int) bool
{
if (transferState != TransferState.Active || result == TransferResult.Pending
|| result == TransferResult.Disconnected || result == TransferResult.Cancelled
|| actualLength < 0 || actualLength > submittedLength) {
return false
}
if (submittedDirection == TransferDirection.DeviceToHost) {
if (actualLength != storage.GetLength()) {
return false
}
} else {
if (result == TransferResult.Success && actualLength != submittedLength) {
return false
}
}
Finish(result, actualLength)
return true
}
/**
* Advances one modeled millisecond; expires active work exactly at its deadline.
*/
public fn AdvanceFrame()
{
if (transferState == TransferState.Active) {
framesRemaining--
if (framesRemaining == 0) {
Finish(TransferResult.Timeout, ReceivedLength())
}
}
}
/**
* Resets the test lifecycle and disconnects; no old completion survives.
*/
public fn Reset()
{
storage.Clear()
connected = false
speed = UsbSpeed.Unknown
transferState = TransferState.Idle
result = TransferResult.Pending
actualLength = 0
framesRemaining = 0
}
/**
* Returns the injected input prefix, or zero for output not yet acknowledged.
*/
fn ReceivedLength() int
{
if (submittedDirection == TransferDirection.DeviceToHost) {
return storage.GetLength()
}
return 0
}
/**
* Publishes data before exposing terminal state; the serialized caller owns mutation.
*/
fn Finish(result: TransferResult, actualLength: int)
{
storage.Publish()
this.result = result
this.actualLength = actualLength
transferState = TransferState.Complete
}
/** Accepts idle toggle resets; this transfer-level model does not simulate PIDs. */
override fn ClearToggle(endpoint: int, inputDirection: bool) bool
{
return transferState == TransferState.Idle && endpoint >= 1 && endpoint <= 15
}
}