//! USB class-code decoding: turn the (class, subclass, protocol) triple a USB device or
//! interface reports in its descriptors into typed values. The device descriptor carries one
//! triple for the whole device, and each interface descriptor carries its own; a class code
//! of zero at the device level defers entirely to the interfaces. Subclass and protocol
//! codes are qualified by the class code — the same value means different things under
//! different classes — so there is no single SubClass or Protocol enum: each class with
//! spec-defined codes gets its own namespace below. Pure reference data (from the USB-IF
//! defined class codes; see https://www.usb.org/defined-class-codes) — no hardware access —
//! so it is shared by kernel discovery and any user-space tool (device naming, driver
//! matching).

// Base class codes (assigned by the USB-IF). The comment on each value notes where the code
// may legally appear: in the device descriptor, in interface descriptors, or both.
pub const Class = enum(u8) {
    // Use class information in the interface descriptors (device descriptor only). Each
    // interface within a configuration specifies its own class information and the various
    // interfaces operate independently.
    per_interface = 0x00,
    // Audio: speakers, microphones, sound cards (interface)
    audio = 0x01,
    // Communications and CDC control: modems, network adapters (both)
    communications = 0x02,
    // Human Interface Device: keyboards, mice, game controllers (interface)
    hid = 0x03,
    // Physical: force-feedback devices (interface)
    physical = 0x05,
    // Image: still-imaging cameras, scanners (interface)
    image = 0x06,
    // Printer (interface)
    printer = 0x07,
    // Mass storage: flash drives, external disks, card readers (interface)
    mass_storage = 0x08,
    // Hub (device descriptor only)
    hub = 0x09,
    // CDC-Data: the data interfaces paired with a communications control interface
    // (interface)
    cdc_data = 0x0A,
    // Smart card readers (interface)
    smart_card = 0x0B,
    // Content security (interface)
    content_security = 0x0D,
    // Video: webcams (interface)
    video = 0x0E,
    // Personal healthcare devices (interface)
    personal_healthcare = 0x0F,
    // Audio/Video devices (interface)
    audio_video = 0x10,
    // Billboard: describes alternate modes a USB Type-C device supports (device descriptor
    // only)
    billboard = 0x11,
    // USB Type-C bridge (interface)
    type_c_bridge = 0x12,
    // USB Bulk Display Protocol devices (interface)
    bulk_display = 0x13,
    // MCTP over USB protocol endpoint devices (interface)
    mctp = 0x14,
    // I3C devices (interface)
    i3c = 0x3C,
    // Diagnostic devices (both)
    diagnostic = 0xDC,
    // Wireless controllers: Bluetooth adapters (interface)
    wireless_controller = 0xE0,
    // Miscellaneous (both)
    miscellaneous = 0xEF,
    // Application-specific: firmware upgrade, IrDA bridges, test and measurement
    // (interface)
    application_specific = 0xFE,
    // Vendor-specific (both)
    vendor_specific = 0xFF,
    _,
};

// Subclass and protocol codes qualified by Class.hub. Hubs have no subclass codes; the
// protocol distinguishes the hub's transaction-translator arrangement.
pub const hub = struct {
    pub const Protocol = enum(u8) {
        // Full-speed hub
        full_speed = 0x00,
        // Hi-speed hub with a single transaction translator
        hi_speed_single_tt = 0x01,
        // Hi-speed hub with multiple transaction translators
        hi_speed_multi_tt = 0x02,
        // SuperSpeed hub (USB 3)
        super_speed = 0x03,
        _,
    };
};

// Subclass and protocol codes qualified by Class.hid.
pub const hid = struct {
    pub const SubClass = enum(u8) {
        // No subclass
        none = 0x00,
        // Boot interface: the device also supports the simplified boot protocol, usable by
        // firmware before a full HID report-descriptor parser is available
        boot = 0x01,
        _,
    };

    // Only meaningful when the subclass is boot
    pub const Protocol = enum(u8) {
        none = 0x00,
        keyboard = 0x01,
        mouse = 0x02,
        _,
    };
};

// Subclass and protocol codes qualified by Class.mass_storage. The subclass identifies the
// command set the device understands; the protocol identifies the transport used to carry
// commands, data, and status over the bus.
pub const mass_storage = struct {
    pub const SubClass = enum(u8) {
        // SCSI command set not reported; de facto, treat as scsi
        not_reported = 0x00,
        // Reduced Block Commands: typically flash devices
        rbc = 0x01,
        // MMC-5 (ATAPI): CD and DVD drives
        atapi = 0x02,
        // QIC-157 tape drives (obsolete)
        qic_157 = 0x03,
        // UFI: floppy disk drives
        ufi = 0x04,
        // SFF-8070i (obsolete)
        sff_8070i = 0x05,
        // Transparent SCSI command set: the common case for flash drives and disks
        scsi = 0x06,
        // LSD FS: negotiated access to large storage devices
        lsd_fs = 0x07,
        // IEEE 1667
        ieee_1667 = 0x08,
        // Vendor-specific
        vendor_specific = 0xFF,
        _,
    };

    pub const Protocol = enum(u8) {
        // Control/Bulk/Interrupt with command completion interrupt
        cbi_completion_interrupt = 0x00,
        // Control/Bulk/Interrupt without command completion interrupt
        cbi = 0x01,
        // Bulk-only transport: the common case for flash drives and disks
        bulk_only = 0x50,
        // USB attached SCSI
        uas = 0x62,
        // Vendor-specific
        vendor_specific = 0xFF,
        _,
    };
};

// Subclass and protocol codes qualified by Class.communications (CDC). The protocol codes
// are model-specific; the useful invariant is the subclass, which selects the control model
// the interface implements.
pub const communications = struct {
    pub const SubClass = enum(u8) {
        // Direct line control model
        direct_line = 0x01,
        // Abstract control model: USB modems and serial adapters
        abstract_control = 0x02,
        // Telephone control model
        telephone = 0x03,
        // Multi-channel control model
        multi_channel = 0x04,
        // CAPI control model
        capi = 0x05,
        // Ethernet networking control model
        ethernet = 0x06,
        // ATM networking control model
        atm = 0x07,
        // Wireless handset control model
        wireless_handset = 0x08,
        // Device management
        device_management = 0x09,
        // Mobile direct line model
        mobile_direct_line = 0x0A,
        // OBEX
        obex = 0x0B,
        // Ethernet emulation model
        ethernet_emulation = 0x0C,
        // Network control model
        network_control = 0x0D,
        _,
    };
};

// Subclass and protocol codes qualified by Class.wireless_controller.
pub const wireless_controller = struct {
    pub const SubClass = enum(u8) {
        // Radio frequency controllers
        radio_frequency = 0x01,
        _,
    };

    // Only meaningful when the subclass is radio_frequency
    pub const Protocol = enum(u8) {
        // Bluetooth programming interface
        bluetooth = 0x01,
        // Ultra-wideband radio control
        ultra_wideband = 0x02,
        // Remote NDIS
        remote_ndis = 0x03,
        // Bluetooth AMP controller
        bluetooth_amp = 0x04,
        _,
    };
};

// Subclass and protocol codes qualified by Class.miscellaneous.
pub const miscellaneous = struct {
    pub const SubClass = enum(u8) {
        // Common class
        common = 0x02,
        _,
    };

    // Only meaningful when the subclass is common
    pub const Protocol = enum(u8) {
        // Interface association descriptor: at the device level, announces that the
        // configuration groups interfaces into functions with IADs
        interface_association = 0x01,
        _,
    };
};

// Subclass and protocol codes qualified by Class.application_specific.
pub const application_specific = struct {
    pub const SubClass = enum(u8) {
        // Device firmware upgrade
        firmware_upgrade = 0x01,
        // IrDA bridge
        irda_bridge = 0x02,
        // Test and measurement
        test_and_measurement = 0x03,
        _,
    };
};

/// Pack a (class, subclass, protocol) triple into one 0xCCSSPP value — the
/// bus-native identity a USB bus driver reports in `ChildAdded.identity` and the
/// device manager matches on (the USB analog of a packed PCI class code). Mirrors
/// `pci_class.ClassCode.pack`, so both sides build/decode the identical u64.
pub fn packTriple(class: u8, subclass: u8, protocol: u8) u64 {
    return (@as(u64, class) << 16) | (@as(u64, subclass) << 8) | protocol;
}

/// The inverse of `packTriple`.
pub fn unpackTriple(triple: u64) struct { class: u8, subclass: u8, protocol: u8 } {
    return .{
        .class = @truncate(triple >> 16),
        .subclass = @truncate(triple >> 8),
        .protocol = @truncate(triple),
    };
}

test "class codes match the USB-IF assignments" {
    const std = @import("std");
    const expectEqual = std.testing.expectEqual;

    try expectEqual(0x03, @intFromEnum(Class.hid));
    try expectEqual(0x09, @intFromEnum(Class.hub));
    try expectEqual(0xFF, @intFromEnum(Class.vendor_specific));

    // A typical flash drive: mass storage, transparent SCSI, bulk-only transport.
    try expectEqual(0x06, @intFromEnum(mass_storage.SubClass.scsi));
    try expectEqual(0x50, @intFromEnum(mass_storage.Protocol.bulk_only));

    // A boot keyboard: HID, boot subclass, keyboard protocol.
    try expectEqual(0x01, @intFromEnum(hid.SubClass.boot));
    try expectEqual(0x01, @intFromEnum(hid.Protocol.keyboard));

    // Class codes are non-exhaustive: unlisted values pass through undamaged.
    const unknown: Class = @enumFromInt(0x42);
    try expectEqual(0x42, @intFromEnum(unknown));

    _ = hub.Protocol.hi_speed_multi_tt;
    _ = communications.SubClass.abstract_control;
    _ = wireless_controller.Protocol.bluetooth;
    _ = miscellaneous.Protocol.interface_association;
    _ = application_specific.SubClass.firmware_upgrade;
}

test "packTriple / unpackTriple round-trip the identity a bus driver reports" {
    const std = @import("std");
    const expectEqual = std.testing.expectEqual;

    // A boot keyboard interface: HID / boot / keyboard.
    const keyboard = packTriple(
        @intFromEnum(Class.hid),
        @intFromEnum(hid.SubClass.boot),
        @intFromEnum(hid.Protocol.keyboard),
    );
    try expectEqual(@as(u64, 0x03_01_01), keyboard);

    // A flash drive interface: mass storage / SCSI / bulk-only.
    const storage = packTriple(
        @intFromEnum(Class.mass_storage),
        @intFromEnum(mass_storage.SubClass.scsi),
        @intFromEnum(mass_storage.Protocol.bulk_only),
    );
    try expectEqual(@as(u64, 0x08_06_50), storage);

    const parts = unpackTriple(storage);
    try expectEqual(@as(u8, 0x08), parts.class);
    try expectEqual(@as(u8, 0x06), parts.subclass);
    try expectEqual(@as(u8, 0x50), parts.protocol);
}
