//! USB device-framework wire ABI: the set-up packets, standard requests, and standard
//! descriptors every USB device speaks over its default control pipe, as defined by chapter 9
//! of the USB 2.0 specification (see https://wiki.osdev.org/Universal_Serial_Bus). Pure data
//! definitions — no hardware access — shared by the host-controller bus drivers (which build
//! the requests) and anything that parses what devices return (device naming, driver
//! matching, configuration). The structs mirror the wire byte-for-byte: multi-byte fields are
//! little-endian and align(1), so a descriptor can be bit-cast straight out of a transfer
//! buffer at any offset, and bitmap bytes are packed structs so no caller ever needs a magic
//! mask. Class, subclass, and protocol code tables live in usb-ids.zig.

pub const DeviceState = enum(u8) {
    // Immediately after the USB device is attached to the USB system, it is in this state.
    // The USB specifications do not define the state of a USB device that is detached from
    // a USB system.
    attached,
    // A device is in this state after it has both been attached to the bus, and the VBUS line is
    // applied to the device (the host controller drives the VBUS at +5V, however this is only
    // particularly important for hardware developers). In this state, the device must not respond
    // to any bus transactions. The USB specification recognizes three potential scenarios with
    // respect to how a device draws power:
    //    - Self-Powered Devices draw power from an external power source (e.g, a USB printer plugs
    //      into the wall as well as a USB port). Although the device may be considered
    //      technically "powered" even before attachment to the USB, it is still only considered
    //      powered after the VBUS line is applied to the device.
    //    - Bus-Powered Devices draw power solely from the USB up to 100mA.
    //    - Self- or Bus-Powered Devices may draw power from either the bus or an external power
    //      source, depending on the configuration. These devices may change power source at any
    //      time. If a device is currently self-powered and requires more than 100mA of power, but
    //      switches to being bus-powered, then the device must return to the Address state.
    powered,
    // A device in the powered state enters the default state after receiving a bus reset. In this
    // state, the device is addressable at the default, reserved address of 0. At this point, the
    // device is operating at the correct speed. The host is expected to allow 10 milliseconds
    // before expecting the device to respond to data transfers after reset.
    default,
    // A device enters this state after the host assigns it an address via the default control pipe,
    // which is always accessible whether the device's address has been set or not.
    address,
    // A device is in this state after the host examines its possible configurations and selects
    // one. All endpoint's data toggle bits are initialized to zero when a device enters this state.
    configured,
    // When no traffic is observed on the bus for a period of 1 millisecond, a USB device enters
    // this state, characterized by its low power consumption. The device's address and
    // configuration settings are maintained while suspended. A device exits the suspended state as
    // soon as it begins seeing bus activity again. The host is expected to allow 10 milliseconds
    // before expecting the device to respond to data transfers after resume.
    suspended,
};

pub const RequestCode = enum(u8) {
    get_status = 0,
    clear_feature = 1,
    set_feature = 3,
    set_address = 5,
    get_descriptor = 6,
    set_descriptor = 7,
    get_configuration = 8,
    set_configuration = 9,
    get_interface = 10,
    set_interface = 11,
    sync_frame = 12,
    // Non-exhaustive: class-specific requests (HID, mass storage) reuse this byte
    // field with codes from their own class's namespace — see the class-request
    // constructors below. Some class codes numerically coincide with a standard
    // one; the wire byte is what matters, and the constructors set it explicitly.
    _,
};

// Direction of an endpoint, from the host's point of view
pub const EndpointDirection = enum(u1) {
    out = 0,
    in = 1,
};

// Identifier newtypes: distinct wire-sized types for values that identify something on the
// device rather than count something. Each is a non-exhaustive enum whose values originate
// in the descriptors below and flow, still typed, into the standard request constructors —
// so an interface number can never be passed where a configuration value is expected.

// The bus address of a device, assigned by the host with SET_ADDRESS. Addresses are 7 bits
// wide.
pub const DeviceAddress = enum(u7) {
    // The default address every device answers at after a reset, until SET_ADDRESS
    // completes
    default = 0,
    _,
};

// Identifies a configuration; from ConfigurationDescriptor.configuration_value.
pub const ConfigurationValue = enum(u8) {
    // Not configured: returned by GET_CONFIGURATION while the device is in the address
    // state, and passed to SET_CONFIGURATION to return a configured device to the address
    // state
    none = 0,
    _,
};

// Identifies an interface within a configuration; from
// InterfaceDescriptor.interface_number.
pub const InterfaceNumber = enum(u8) { _ };

// Selects between the alternate settings of one interface; from
// InterfaceDescriptor.alternate_setting.
pub const AlternateSetting = enum(u8) {
    // The default setting of an interface
    default = 0,
    _,
};

// The number of an endpoint within a device, 4 bits wide. The direction bit carried
// alongside it tells the two endpoints sharing a number apart.
pub const EndpointNumber = enum(u4) {
    // Endpoint zero: the default control pipe every device provides
    default_control = 0,
    _,
};

// Index of a STRING descriptor, stored in descriptors that reference a string and passed to
// GET_DESCRIPTOR to read it.
pub const StringIndex = enum(u8) {
    // The device has no string descriptor for this field
    none = 0,
    _,
};

// Characteristics of a device request (the bmRequestType field of a set-up packet). Fields are
// declared least-significant first: recipient occupies bits 4...0, kind bits 6...5, and
// direction bit 7.
pub const RequestType = packed struct(u8) {
    // The recipient of the request (values 4...31 are reserved)
    recipient: Recipient,
    // The type of the request
    kind: Kind,
    // Data transfer direction. The value of this bit is ignored when length is zero.
    direction: Direction,

    pub const Recipient = enum(u5) {
        device = 0,
        interface = 1,
        endpoint = 2,
        other = 3,
    };

    pub const Kind = enum(u2) {
        standard = 0,
        class = 1,
        vendor = 2,
        reserved = 3,
    };

    pub const Direction = enum(u1) {
        host_to_device = 0,
        device_to_host = 1,
    };
};

pub const Request = extern struct {
    // Characteristics of the request
    request_type: RequestType,
    // Specific request
    request_code: RequestCode,
    // Word-sized field that may (or may not) serve as a parameter to the request, depending
    // on the specific request. For GET_DESCRIPTOR and SET_DESCRIPTOR, bit-cast a
    // DescriptorValue into this field.
    value: u16 align(1),
    // Word-sized field that may (or may not) serve as a parameter to the request, depending
    // on the specific request. Typically this field holds an index or an offset value. When
    // request_type specifies an endpoint or an interface as the recipient, bit-cast an
    // EndpointIndex or an InterfaceIndex into this field.
    index: u16 align(1),
    // Number of bytes to transfer if there is a DATA stage.
    //   - If this field is non-zero, and request_type indicates a transfer from
    //     device-to-host, then the device must never return more than length bytes of data.
    //     However, a device may return less.
    //   - If this field is non-zero, and request_type indicates a transfer from
    //     host-to-device, then the host must send exactly length bytes of data. If the host
    //     sends more than length bytes, the behavior of the device is undefined.
    length: u16 align(1),

    // The format of the index field when request_type specifies an endpoint as the
    // recipient. The host should always set the direction bit to zero (but the device
    // should accept either value) when the endpoint is part of a control pipe.
    pub const EndpointIndex = packed struct(u16) {
        // Endpoint number
        number: EndpointNumber,
        // Reserved (reset to zero)
        reserved: u3 = 0,
        // Selects the OUT or the IN endpoint with the specified endpoint number
        direction: EndpointDirection,
        // Reserved (reset to zero)
        reserved_high: u8 = 0,
    };

    // The format of the index field when request_type specifies an interface as the
    // recipient.
    pub const InterfaceIndex = packed struct(u16) {
        // Interface number
        number: u8,
        // Reserved (reset to zero)
        reserved: u8 = 0,
    };

    // The format of the value field of GET_DESCRIPTOR and SET_DESCRIPTOR requests: the
    // descriptor type in the high byte, and the descriptor index in the low byte. The index
    // is used to select a specific descriptor (only for CONFIGURATION and STRING
    // descriptors) when several descriptors of that type are implemented by a device.
    pub const DescriptorValue = packed struct(u16) {
        // Descriptor index
        index: u8 = 0,
        // Descriptor type
        kind: DescriptorType,
    };
};

// Feature selectors, used as the value field of CLEAR_FEATURE and SET_FEATURE requests. The
// comment on each value notes the recipient the selector applies to.
pub const FeatureSelector = enum(u16) {
    // Halts an endpoint (recipient: endpoint)
    endpoint_halt = 0,
    // Enables or disables the device's remote wakeup capability (recipient: device)
    device_remote_wakeup = 1,
    // Puts a hi-speed device into a test mode, selected by a TestMode value in the high
    // byte of the index field (recipient: device)
    test_mode = 2,
};

// Test mode selectors, passed in the high byte of the index field of a SET_FEATURE request
// with the test_mode feature selector. Values 06h...3Fh are reserved for standard test
// selectors and C0h...FFh for vendor-specific test modes; all other unlisted values are
// reserved.
pub const TestMode = enum(u8) {
    test_j = 0x01,
    test_k = 0x02,
    test_se0_nak = 0x03,
    test_packet = 0x04,
    test_force_enable = 0x05,
    _,
};

// The two bytes returned by a GET_STATUS request directed at a device. Fields are declared
// least-significant first.
pub const DeviceStatus = packed struct(u16) {
    // Whether the device is currently self-powered (as opposed to bus-powered). This bit
    // cannot be changed with the SET_FEATURE or CLEAR_FEATURE requests.
    self_powered: bool,
    // Whether the device is currently enabled to request remote wakeup. Changed with the
    // SET_FEATURE and CLEAR_FEATURE requests using the device_remote_wakeup feature
    // selector.
    remote_wakeup: bool,
    // Reserved (reset to zero)
    reserved: u14,
};

// The two bytes returned by a GET_STATUS request directed at an endpoint. (A GET_STATUS
// request directed at an interface returns two bytes that are entirely reserved.)
pub const EndpointStatus = packed struct(u16) {
    // Whether the endpoint is currently halted. Set with the SET_FEATURE request using the
    // endpoint_halt feature selector, and cleared with CLEAR_FEATURE.
    halted: bool,
    // Reserved (reset to zero)
    reserved: u15,
};

// A target for the standard requests that may be directed at the device, an interface, or
// an endpoint.
pub const Target = union(enum) {
    device,
    interface: InterfaceNumber,
    endpoint: Request.EndpointIndex,

    fn recipient(target: Target) RequestType.Recipient {
        return switch (target) {
            .device => .device,
            .interface => .interface,
            .endpoint => .endpoint,
        };
    }

    fn index(target: Target) u16 {
        return switch (target) {
            .device => 0,
            .interface => |number| @intFromEnum(number),
            .endpoint => |endpoint| @bitCast(endpoint),
        };
    }
};

// Constructors for the standard device requests, one per RequestCode. Each returns a
// ready-to-send set-up packet with the request_type, value, index, and length fields the
// specification prescribes for that request.

// Reads the status of the given target: bit-cast the two bytes the device returns into a
// DeviceStatus or an EndpointStatus. (The two bytes returned for an interface are entirely
// reserved.)
pub fn getStatus(target: Target) Request {
    return .{
        .request_type = .{
            .recipient = target.recipient(),
            .kind = .standard,
            .direction = .device_to_host,
        },
        .request_code = .get_status,
        .value = 0,
        .index = target.index(),
        .length = 2,
    };
}

// Clears or disables the given feature. A device cannot be taken out of a test mode with
// this request; test_mode is only cleared by cycling power.
pub fn clearFeature(feature: FeatureSelector, target: Target) Request {
    return .{
        .request_type = .{
            .recipient = target.recipient(),
            .kind = .standard,
            .direction = .host_to_device,
        },
        .request_code = .clear_feature,
        .value = @intFromEnum(feature),
        .index = target.index(),
        .length = 0,
    };
}

// Sets or enables the given feature. For the test_mode feature selector, use setTestMode
// instead: the test selector rides in the high byte of the index field.
pub fn setFeature(feature: FeatureSelector, target: Target) Request {
    return .{
        .request_type = .{
            .recipient = target.recipient(),
            .kind = .standard,
            .direction = .host_to_device,
        },
        .request_code = .set_feature,
        .value = @intFromEnum(feature),
        .index = target.index(),
        .length = 0,
    };
}

// Puts a hi-speed device into the given test mode: a SET_FEATURE request with the test_mode
// feature selector and the test selector in the high byte of the index field.
pub fn setTestMode(mode: TestMode) Request {
    return .{
        .request_type = .{
            .recipient = .device,
            .kind = .standard,
            .direction = .host_to_device,
        },
        .request_code = .set_feature,
        .value = @intFromEnum(FeatureSelector.test_mode),
        .index = @as(u16, @intFromEnum(mode)) << 8,
        .length = 0,
    };
}

// Assigns the device its bus address, moving it from the default state to the address
// state. The device does not answer at the new address until the status stage of this
// request completes.
pub fn setAddress(address: DeviceAddress) Request {
    return .{
        .request_type = .{
            .recipient = .device,
            .kind = .standard,
            .direction = .host_to_device,
        },
        .request_code = .set_address,
        .value = @intFromEnum(address),
        .index = 0,
        .length = 0,
    };
}

// Reads a descriptor from the device.
//   - descriptor_index selects among descriptors of the same type, and is only used for
//     configuration and string descriptors.
//   - language_id selects the language of a string descriptor, and is zero otherwise.
//   - length is the number of bytes to read; a device never returns more than length bytes,
//     but may return less if the descriptor is shorter.
pub fn getDescriptor(kind: DescriptorType, descriptor_index: u8, language_id: u16, length: u16) Request {
    return .{
        .request_type = .{
            .recipient = .device,
            .kind = .standard,
            .direction = .device_to_host,
        },
        .request_code = .get_descriptor,
        .value = @bitCast(Request.DescriptorValue{ .index = descriptor_index, .kind = kind }),
        .index = language_id,
        .length = length,
    };
}

// Updates an existing descriptor or adds a new one (optional; many devices do not support
// this request). The parameters mirror getDescriptor; the descriptor itself is sent in the
// DATA stage.
pub fn setDescriptor(kind: DescriptorType, descriptor_index: u8, language_id: u16, length: u16) Request {
    return .{
        .request_type = .{
            .recipient = .device,
            .kind = .standard,
            .direction = .host_to_device,
        },
        .request_code = .set_descriptor,
        .value = @bitCast(Request.DescriptorValue{ .index = descriptor_index, .kind = kind }),
        .index = language_id,
        .length = length,
    };
}

// Reads the currently active configuration: @enumFromInt the byte the device returns into a
// ConfigurationValue, which is none while the device is not configured.
pub fn getConfiguration() Request {
    return .{
        .request_type = .{
            .recipient = .device,
            .kind = .standard,
            .direction = .device_to_host,
        },
        .request_code = .get_configuration,
        .value = 0,
        .index = 0,
        .length = 1,
    };
}

// Selects the configuration with the given configuration_value (from
// ConfigurationDescriptor.configuration_value), moving the device from the address state to
// the configured state. Selecting none returns the device to the address state.
pub fn setConfiguration(configuration_value: ConfigurationValue) Request {
    return .{
        .request_type = .{
            .recipient = .device,
            .kind = .standard,
            .direction = .host_to_device,
        },
        .request_code = .set_configuration,
        .value = @intFromEnum(configuration_value),
        .index = 0,
        .length = 0,
    };
}

// Reads the alternate setting currently selected for the given interface: @enumFromInt the
// byte the device returns into an AlternateSetting.
pub fn getInterface(interface: InterfaceNumber) Request {
    return .{
        .request_type = .{
            .recipient = .interface,
            .kind = .standard,
            .direction = .device_to_host,
        },
        .request_code = .get_interface,
        .value = 0,
        .index = @intFromEnum(interface),
        .length = 1,
    };
}

// Selects an alternate setting (from InterfaceDescriptor.alternate_setting) for the given
// interface.
pub fn setInterface(interface: InterfaceNumber, alternate_setting: AlternateSetting) Request {
    return .{
        .request_type = .{
            .recipient = .interface,
            .kind = .standard,
            .direction = .host_to_device,
        },
        .request_code = .set_interface,
        .value = @intFromEnum(alternate_setting),
        .index = @intFromEnum(interface),
        .length = 0,
    };
}

// Reads the two-byte number of the frame in which the given isochronous endpoint's
// repeating pattern of transfers begins.
pub fn syncFrame(endpoint: Request.EndpointIndex) Request {
    return .{
        .request_type = .{
            .recipient = .endpoint,
            .kind = .standard,
            .direction = .device_to_host,
        },
        .request_code = .sync_frame,
        .value = 0,
        .index = @bitCast(endpoint),
        .length = 2,
    };
}

// Class-specific requests. These carry a `kind = .class` request_type and a
// request_code from the interface's class namespace (not the standard
// RequestCode set above); the code is written into the same byte field, which
// is why RequestCode is non-exhaustive. Each is directed at an interface, whose
// number rides in the index field.

// The HID class request codes (USB HID 1.11 §7.2). Only the ones danos issues
// are named; the field on the wire is the raw byte.
pub const HidRequestCode = enum(u8) {
    get_report = 0x01,
    get_idle = 0x02,
    get_protocol = 0x03,
    set_report = 0x09,
    set_idle = 0x0A,
    set_protocol = 0x0B,
};

// The two protocols a boot-capable HID device can run (USB HID 1.11 §7.2.5).
// A driver selects `boot` for the simplified fixed-format boot report, usable
// before a full report-descriptor parser exists.
pub const HidProtocol = enum(u8) {
    boot = 0,
    report = 1,
};

// SET_PROTOCOL: choose the boot or report protocol on a HID interface.
pub fn setProtocol(interface: InterfaceNumber, protocol: HidProtocol) Request {
    return .{
        .request_type = .{ .recipient = .interface, .kind = .class, .direction = .host_to_device },
        .request_code = @enumFromInt(@intFromEnum(HidRequestCode.set_protocol)),
        .value = @intFromEnum(protocol),
        .index = @intFromEnum(interface),
        .length = 0,
    };
}

// SET_IDLE: bound a HID interface's report rate. `duration` is in 4 ms units
// (0 means report only on change); `report_id` selects a report (0 = all).
pub fn setIdle(interface: InterfaceNumber, duration: u8, report_id: u8) Request {
    return .{
        .request_type = .{ .recipient = .interface, .kind = .class, .direction = .host_to_device },
        .request_code = @enumFromInt(@intFromEnum(HidRequestCode.set_idle)),
        .value = (@as(u16, duration) << 8) | report_id,
        .index = @intFromEnum(interface),
        .length = 0,
    };
}

// Bulk-Only Mass Storage Reset (USB MSC BOT §3.1): ready a mass-storage
// interface for the next Command Block Wrapper after a protocol error.
pub fn bulkOnlyMassStorageReset(interface: InterfaceNumber) Request {
    return .{
        .request_type = .{ .recipient = .interface, .kind = .class, .direction = .host_to_device },
        .request_code = @enumFromInt(0xFF),
        .value = 0,
        .index = @intFromEnum(interface),
        .length = 0,
    };
}

// Get Max LUN (USB MSC BOT §3.2): read the highest logical unit number the
// device supports (0 for a single-LUN flash drive). One byte is returned.
pub fn getMaxLun(interface: InterfaceNumber) Request {
    return .{
        .request_type = .{ .recipient = .interface, .kind = .class, .direction = .device_to_host },
        .request_code = @enumFromInt(0xFE),
        .value = 0,
        .index = @intFromEnum(interface),
        .length = 1,
    };
}

pub const DescriptorType = enum(u8) {
    device = 1,
    configuration = 2,
    string = 3,
    interface = 4,
    endpoint = 5,
    device_qualifier = 6,
    other_speed_configuration = 7,
    interface_power = 8,
    _,
};

pub const DeviceDescriptor = extern struct {
    // Size of this descriptor in bytes
    length: u8,
    // DEVICE Descriptor Type
    descriptor_type: DescriptorType,
    // USB Specification Release Number in Binary-Coded Decimal (i.e, 2.10 is expressed as 210h).
    // Identifies the release of the USB Specification with with the device and its
    // descriptors are compliant.
    bcd_usb: u16 align(1),
    // Class code (assigned by the USB-IF)
    //   - This field is reset to zero if each interface within a configuration specifies its own
    //     class information and the various interfaces operate independently.
    //   - A value of FFh in this field indicates the device class is vendor-specific.
    device_class: u8,
    // Subclass Code (assigned by the USB-IF)
    //   - The subclass code of a device is qualified by the class code of that device.
    //   - If device_class is reset to zero, then this field must also be reset to zero.
    //   - When device_class is not set to FFh, then all values for this field are reserved for
    //     assignment by the USB-IF.
    device_subclass: u8,
    // Protocol code (assigned by the USB-IF)
    //   - The protocol code of a device is qualified by both the class and subclass codes of
    //     that device.
    //   - A value of 00h in this field means that the device may specify class-specific
    //     protocols on an interface basis, though this is not a requirement.
    //   - If this field is set to FFh, then the device uses a vendor-specific protocol.
    device_protocol: u8,
    // Maximum packet size for endpoint zero (8, 16, 32, or 64 are the only valid options)
    max_packet_size_0: u8,
    // Vendor ID (assigned by the USB-IF)
    vendor_id: u16 align(1),
    // Product ID (assigned by the USB-IF)
    product_id: u16 align(1),
    // Device release number in binary-coded decimal
    bcd_device: u16 align(1),
    // Index of STRING descriptor describing manufacturer
    manufacturer_index: StringIndex,
    // Index of STRING descriptor describing product
    product_index: StringIndex,
    // Index of STRING descriptor describing the device's serial number
    serial_number_index: StringIndex,
    // Number of possible configurations
    configuration_count: u8,
};

pub const DeviceQualifierDescriptor = extern struct {
    // Size of this descriptor in bytes
    length: u8,
    // DEVICE_QUALIFIER Descriptor Type
    descriptor_type: DescriptorType,
    // USB Specification Release Number in Binary-Coded Decimal (i.e, 2.00 is expressed as 200h).
    // Identifies the release of the USB Specification with with the device and its
    // descriptors are compliant. This field must be at least 0200h.
    bcd_usb: u16 align(1),
    // Class code (assigned by the USB-IF)
    device_class: u8,
    // Subclass Code (assigned by the USB-IF)
    device_subclass: u8,
    // Protocol code (assigned by the USB-IF)
    device_protocol: u8,
    // Maximum packet size for endpoint zero (8, 16, 32, or 64 are the only valid options)
    max_packet_size_0: u8,
    // Number of possible configurations
    configuration_count: u8,
    // Reserved for future uses, must be zero.
    reserved: u8,
};

pub const ConfigurationDescriptor = extern struct {
    // Size of this descriptor in bytes
    length: u8,
    // CONFIGURATION Descriptor Type
    descriptor_type: DescriptorType,
    // The total combined length in bytes of all the descriptors returned with the request for
    // this CONFIGURATION descriptor (including CONFIGURATION, INTERFACE, ENDPOINT, class- and
    // vendor-specific descriptors).
    total_length: u16 align(1),
    // Number of interfaces supported by this configuration
    interface_count: u8,
    // Value which when used as an argument in the SET_CONFIGURATION request, causes the device
    // to assume the configuration described by this descriptor.
    configuration_value: ConfigurationValue,
    // Index of STRING descriptor describing this configuration.
    configuration_index: StringIndex,
    // Configuration Characteristics
    attributes: Attributes,
    // Maximum power consumption of this device from the bus when fully operational and using
    // this configuration. Expressed in units of 2mA (i.e., a value of 50 in this field
    // indicates 100mA).
    //   - A device reports with the attributes field whether the configuration is bus- or
    //     self-powered, but the device status (retrieved with a GET_STATUS request) reports
    //     whether the device is currently self-powered.
    //   - If a device is disconnected from an external power source, it may not draw more
    //     power from the bus than specified in this field.
    max_power: u8,

    // Configuration characteristics. Fields are declared least-significant first.
    pub const Attributes = packed struct(u8) {
        // Reserved, reset to zero (D4...0)
        reserved: u5,
        // Whether Remote Wakeup is supported by this configuration (D5)
        remote_wakeup: bool,
        // Self-Powered (D6)
        //   - false: Device runs on power supplied by the bus
        //   - true: Device provides a local power source; if max_power is non-zero, the
        //     device also may use bus power.
        self_powered: bool,
        // Reserved, must be set to one for historical reasons (D7)
        reserved_one: u1,
    };
};

// This descriptor describes the configuration of a high-speed device if it were operating at
// its alternative speed. The structure of the OTHER_SPEED_CONFIGURATION is identical to that
// of the CONFIGURATION descriptor; the only difference is that the descriptor_type field
// reflects that the descriptor is an OTHER_SPEED_CONFIGURATION descriptor.
pub const OtherSpeedConfigurationDescriptor = ConfigurationDescriptor;

pub const InterfaceDescriptor = extern struct {
    // Size of this descriptor in bytes
    length: u8,
    // INTERFACE Descriptor Type
    descriptor_type: DescriptorType,
    // Number of this interface. Zero-based value which identifies the index of this interface
    // in the array of interfaces supported within a configuration.
    interface_number: InterfaceNumber,
    // Value used to select the alternate settings described by this INTERFACE descriptor for
    // the interface with the interface_number in the previous field. This value is zero if
    // this descriptor describes the default settings for a particular interface.
    alternate_setting: AlternateSetting,
    // Number of endpoints used by this interface, not including endpoint zero.
    endpoint_count: u8,
    // Class code (assigned by the USB-IF)
    //   - A value of zero here is reserved for future standardization.
    //   - If this value is FFh, the interface class is vendor-specific.
    //   - All other values are reserved for assignment by the USB-IF.
    interface_class: u8,
    // Subclass code (assigned by the USB-IF)
    //   - The subclass code in this field is qualified by the value of the interface_class
    //     field.
    //   - If interface_class is reset to zero, then this field must also be reset to zero.
    //   - If interface_class is not set to the value of FFh, then all values of this field are
    //     reserved for assignment by the USB-IF.
    interface_subclass: u8,
    // Protocol code (assigned by the USB-IF)
    //   - The protocol code in this field is qualified by the values of the interface_class
    //     and interface_subclass fields.
    //   - If an interface supports class-specific requests, then this field identifies the
    //     protocols that the device uses as defined by the specifications of the device class.
    //   - If this field is reset to zero, then the device does not use a class-specific
    //     protocol on this interface.
    //   - If this field is set to FFh, then the device uses a vendor-specific protocol on
    //     this interface.
    interface_protocol: u8,
    // Index of STRING descriptor describing this interface
    interface_index: StringIndex,
};

pub const EndpointDescriptor = extern struct {
    // Size of this descriptor in bytes
    length: u8,
    // ENDPOINT Descriptor Type
    descriptor_type: DescriptorType,
    // The address of the endpoint on the USB device described by this descriptor
    endpoint_address: Address,
    // The endpoint's attributes
    attributes: Attributes,
    // Maximum packet size that this endpoint is capable of sending or receiving. For
    // isochronous endpoints, this value is used to reserve bus time; the pipe, however, may
    // not always use all of the reserved bus time.
    max_packet_size: MaxPacketSize align(1),
    // Interval for polling a device during a data transfer, expressed in units of microframes
    // for high-speed devices, and frames for low- and full-speed devices. The exact meaning of
    // the value in this field depends on the endpoint type and the operating speed of the
    // device:
    //   - Full- and High-speed isochronous endpoints, and high-speed interrupt endpoints:
    //     This field must be in the range from 1 to 16, and is used to calculate the period
    //     as 2^(interval - 1). That is, a value of 4 calculates to 2^(4 - 1) = 2^3 = 8.
    //   - Full- and Low-speed interrupt endpoints: This field must be in the range from
    //     1 to 255.
    //   - High-speed bulk and control OUT endpoints: This field must be in the range from
    //     0 to 255, and specifies the maximum NAK rate of the endpoint. A value of zero
    //     indicates that the endpoint never NAKs; other values indicate at most 1 NAK each
    //     interval number of microframes.
    interval: u8,

    // The address of an endpoint. Fields are declared least-significant first.
    pub const Address = packed struct(u8) {
        // Endpoint Number (D3...0)
        number: EndpointNumber,
        // Reserved, reset to zero (D6...4)
        reserved: u3,
        // Direction, ignored for control endpoints (D7)
        direction: EndpointDirection,
    };

    // An endpoint's attributes. Fields are declared least-significant first.
    pub const Attributes = packed struct(u8) {
        // Transfer Type (D1...0)
        transfer_type: TransferType,
        // Synchronization Type; isochronous endpoints only, reserved and reset to zero for
        // other endpoint types (D3...2)
        synchronization: Synchronization,
        // Usage Type; isochronous endpoints only, reserved and reset to zero for other
        // endpoints (D5...4)
        usage: Usage,
        // Reserved, reset to zero (D7...6)
        reserved: u2,
    };

    pub const TransferType = enum(u2) {
        control = 0,
        isochronous = 1,
        bulk = 2,
        interrupt = 3,
    };

    pub const Synchronization = enum(u2) {
        none = 0,
        asynchronous = 1,
        adaptive = 2,
        synchronous = 3,
    };

    pub const Usage = enum(u2) {
        data = 0,
        feedback = 1,
        implicit_feedback_data = 2,
        _,
    };

    // The maximum packet size of an endpoint. Fields are declared least-significant first.
    pub const MaxPacketSize = packed struct(u16) {
        // Maximum packet size in bytes (bits 10...0)
        size: u11,
        // Number of additional transaction opportunities per microframe, for high-speed
        // isochronous and interrupt endpoints; reserved and reset to zero for other
        // endpoints (bits 12...11)
        additional_transactions: AdditionalTransactions,
        // Reserved, must be reset to zero (bits 15...13)
        reserved: u3,
    };

    pub const AdditionalTransactions = enum(u2) {
        // None (1 transaction per microframe)
        none = 0,
        // 1 additional (2 transactions per microframe)
        one = 1,
        // 2 additional (3 transactions per microframe)
        two = 2,
        _,
    };
};

// A STRING descriptor at index zero returns the list of LANGID codes supported by the
// device; all other indices return a Unicode string. Both forms start with this two-byte
// header, followed by the variable-length payload:
//   - index 0: an array of two-byte LANGID codes (wLangID[0] through wLangID[x])
//   - other indices: a Unicode string of N bytes
pub const StringDescriptor = extern struct {
    // Size of this descriptor in bytes
    length: u8,
    // STRING Descriptor Type
    descriptor_type: DescriptorType,
};

const std = @import("std");

test "wire sizes and offsets match the specification" {
    const expectEqual = std.testing.expectEqual;

    try expectEqual(8, @sizeOf(Request));
    try expectEqual(18, @sizeOf(DeviceDescriptor));
    try expectEqual(10, @sizeOf(DeviceQualifierDescriptor));
    try expectEqual(9, @sizeOf(ConfigurationDescriptor));
    try expectEqual(9, @sizeOf(InterfaceDescriptor));
    try expectEqual(7, @sizeOf(EndpointDescriptor));
    try expectEqual(2, @sizeOf(StringDescriptor));

    try expectEqual(2, @offsetOf(DeviceDescriptor, "bcd_usb"));
    try expectEqual(8, @offsetOf(DeviceDescriptor, "vendor_id"));
    try expectEqual(17, @offsetOf(DeviceDescriptor, "configuration_count"));
    try expectEqual(2, @offsetOf(ConfigurationDescriptor, "total_length"));
    try expectEqual(4, @offsetOf(EndpointDescriptor, "max_packet_size"));
}

test "bitmap packings match the specification" {
    const expectEqual = std.testing.expectEqual;
    const expect = std.testing.expect;

    // bmRequestType for GET_DESCRIPTOR: device-to-host | standard | device = 80h
    const request_type = RequestType{
        .recipient = .device,
        .kind = .standard,
        .direction = .device_to_host,
    };
    try expectEqual(0x80, @as(u8, @bitCast(request_type)));

    // wValue for GET_DESCRIPTOR(CONFIGURATION, index 0) = 0200h
    const descriptor_value = Request.DescriptorValue{ .kind = .configuration };
    try expectEqual(0x0200, @as(u16, @bitCast(descriptor_value)));

    // wIndex for the IN endpoint 1 = 0081h
    const endpoint_index = Request.EndpointIndex{ .number = @enumFromInt(1), .direction = .in };
    try expectEqual(0x0081, @as(u16, @bitCast(endpoint_index)));

    // Endpoint address 81h = IN endpoint 1
    const address: EndpointDescriptor.Address = @bitCast(@as(u8, 0x81));
    try expectEqual(1, @intFromEnum(address.number));
    try expectEqual(.in, address.direction);

    // Endpoint attributes 03h = interrupt transfer
    const attributes: EndpointDescriptor.Attributes = @bitCast(@as(u8, 0x03));
    try expectEqual(.interrupt, attributes.transfer_type);

    // wMaxPacketSize 0008h = 8 bytes, no additional transactions
    const max_packet_size: EndpointDescriptor.MaxPacketSize = @bitCast(@as(u16, 0x0008));
    try expectEqual(8, max_packet_size.size);
    try expectEqual(.none, max_packet_size.additional_transactions);

    // Configuration attributes C0h = self-powered, with the historical D7 bit set
    const configuration_attributes: ConfigurationDescriptor.Attributes = @bitCast(@as(u8, 0xC0));
    try expect(configuration_attributes.self_powered);
    try expect(!configuration_attributes.remote_wakeup);
    try expectEqual(1, configuration_attributes.reserved_one);

    // GET_STATUS words: device 0001h = self-powered; endpoint 0001h = halted
    const device_status: DeviceStatus = @bitCast(@as(u16, 0x0001));
    try expect(device_status.self_powered and !device_status.remote_wakeup);
    const endpoint_status: EndpointStatus = @bitCast(@as(u16, 0x0001));
    try expect(endpoint_status.halted);

    // DescriptorType is non-exhaustive: class-specific values (HID = 21h) pass through
    const hid_type: DescriptorType = @enumFromInt(0x21);
    try expectEqual(0x21, @intFromEnum(hid_type));
    try expect(hid_type != .device);
}

pub fn expectRequestBytes(request: Request, expected: [8]u8) !void {
    try std.testing.expectEqualSlices(u8, &expected, std.mem.asBytes(&request));
}

test "standard request constructors encode the specification's set-up packets" {
    try expectRequestBytes(getStatus(.device), .{ 0x80, 0, 0, 0, 0, 0, 2, 0 });
    try expectRequestBytes(getStatus(.{ .interface = @enumFromInt(3) }), .{ 0x81, 0, 0, 0, 3, 0, 2, 0 });
    try expectRequestBytes(getStatus(.{ .endpoint = .{ .number = @enumFromInt(2), .direction = .in } }), .{ 0x82, 0, 0, 0, 0x82, 0, 2, 0 });
    try expectRequestBytes(clearFeature(.endpoint_halt, .{ .endpoint = .{ .number = @enumFromInt(1), .direction = .out } }), .{ 0x02, 1, 0, 0, 0x01, 0, 0, 0 });
    try expectRequestBytes(setFeature(.device_remote_wakeup, .device), .{ 0x00, 3, 1, 0, 0, 0, 0, 0 });
    try expectRequestBytes(setTestMode(.test_packet), .{ 0x00, 3, 2, 0, 0, 0x04, 0, 0 });
    try expectRequestBytes(setAddress(@enumFromInt(5)), .{ 0x00, 5, 5, 0, 0, 0, 0, 0 });
    try expectRequestBytes(getDescriptor(.device, 0, 0, 18), .{ 0x80, 6, 0, 1, 0, 0, 18, 0 });
    try expectRequestBytes(getDescriptor(.string, 2, 0x0409, 255), .{ 0x80, 6, 2, 3, 0x09, 0x04, 255, 0 });
    try expectRequestBytes(setDescriptor(.string, 2, 0x0409, 16), .{ 0x00, 7, 2, 3, 0x09, 0x04, 16, 0 });
    try expectRequestBytes(getConfiguration(), .{ 0x80, 8, 0, 0, 0, 0, 1, 0 });
    try expectRequestBytes(setConfiguration(@enumFromInt(1)), .{ 0x00, 9, 1, 0, 0, 0, 0, 0 });
    try expectRequestBytes(getInterface(@enumFromInt(2)), .{ 0x81, 10, 0, 0, 2, 0, 1, 0 });
    try expectRequestBytes(setInterface(@enumFromInt(2), @enumFromInt(1)), .{ 0x01, 11, 1, 0, 2, 0, 0, 0 });
    try expectRequestBytes(syncFrame(.{ .number = @enumFromInt(3), .direction = .in }), .{ 0x82, 12, 0, 0, 0x83, 0, 2, 0 });
}

test "class request constructors encode the specification's set-up packets" {
    // bmRequestType for a host-to-device class request to an interface = 0x21;
    // device-to-host = 0xA1. The request_code byte is the class code, not a
    // standard one — SET_PROTOCOL 0x0B, SET_IDLE 0x0A, BOT reset 0xFF, Max LUN 0xFE.
    try expectRequestBytes(setProtocol(@enumFromInt(0), .boot), .{ 0x21, 0x0B, 0, 0, 0, 0, 0, 0 });
    try expectRequestBytes(setProtocol(@enumFromInt(1), .report), .{ 0x21, 0x0B, 1, 0, 1, 0, 0, 0 });
    try expectRequestBytes(setIdle(@enumFromInt(1), 0, 0), .{ 0x21, 0x0A, 0, 0, 1, 0, 0, 0 });
    try expectRequestBytes(bulkOnlyMassStorageReset(@enumFromInt(0)), .{ 0x21, 0xFF, 0, 0, 0, 0, 0, 0 });
    try expectRequestBytes(getMaxLun(@enumFromInt(0)), .{ 0xA1, 0xFE, 0, 0, 0, 0, 1, 0 });
}
