//go:build windows package usb import ( "encoding/binary" "fmt" "unsafe" "golang.org/x/sys/windows" ) // Interface to the usbshare filter driver (driver/windows). // // The structure layouts and IOCTL codes here must match public.h exactly. // They are marshalled by hand on both sides, so a mismatch corrupts memory // rather than failing cleanly — change one, change the other. // GUID_DEVINTERFACE_USBSHARE from public.h. var guidDevInterfaceUsbShare = windows.GUID{ Data1: 0x8f3d2a14, Data2: 0x6c7b, Data3: 0x4e59, Data4: [8]byte{0x9a, 0x1d, 0x3f, 0x5b, 0x7c, 0x8e, 0x2d, 0x40}, } // IOCTL codes, mirroring the USBSHARE_IOCTL macro. const ( fileDeviceUsbShare = 0x8000 methodBuffered = 0 fileAnyAccess = 0 ) func usbShareIOCTL(index uint32) uint32 { return (fileDeviceUsbShare << 16) | (fileAnyAccess << 14) | ((0x800 + index) << 2) | methodBuffered } var ( ioctlClaim = usbShareIOCTL(0) ioctlRelease = usbShareIOCTL(1) ioctlGetDescriptors = usbShareIOCTL(2) ioctlSubmit = usbShareIOCTL(3) ioctlCancel = usbShareIOCTL(4) ioctlSetInterface = usbShareIOCTL(5) ioctlClearHalt = usbShareIOCTL(6) ioctlReset = usbShareIOCTL(7) ) // Transfer types, matching USBSHARE_TRANSFER_* in public.h. const ( winTransferControl = 0 winTransferIsochronous = 1 winTransferBulk = 2 winTransferInterrupt = 3 ) // Directions, matching USBSHARE_DIR_*. const ( winDirOut = 0 winDirIn = 1 ) // winDeviceInfo mirrors USBSHARE_DEVICE_INFO (packed). type winDeviceInfo struct { VendorID uint16 ProductID uint16 BcdDevice uint16 DeviceClass uint8 DeviceSubClass uint8 DeviceProtocol uint8 ConfigurationValue uint8 NumConfigurations uint8 Speed uint32 PortNumber uint32 } // winTransferHeader mirrors USBSHARE_TRANSFER (packed). type winTransferHeader struct { ID uint64 EndpointAddress uint8 Type uint8 Direction uint8 Reserved uint8 BufferLength uint32 Timeout uint32 Setup [8]byte } // winTransferResult mirrors USBSHARE_TRANSFER_RESULT (packed). type winTransferResult struct { ID uint64 Status int32 UsbdStatus uint32 ActualLength uint32 } const ( winTransferHeaderSize = 8 + 1 + 1 + 1 + 1 + 4 + 4 + 8 // 28 winTransferResultSize = 8 + 4 + 4 + 4 // 20 ) // DriverHandle is an open handle to a device claimed through the filter driver. type DriverHandle struct { handle windows.Handle info winDeviceInfo nextID uint64 } // OpenDriverDevice opens the filter driver's interface for a device path and // claims the device. // // Claiming stops the class driver from talking to the device, which is what // lets us drive it — and it is released automatically if this process dies, // because the driver ties the claim to the handle. func OpenDriverDevice(devicePath string) (*DriverHandle, error) { pathPtr, err := windows.UTF16PtrFromString(devicePath) if err != nil { return nil, fmt.Errorf("invalid device path: %w", err) } handle, err := windows.CreateFile( pathPtr, windows.GENERIC_READ|windows.GENERIC_WRITE, windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE, nil, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL, 0, ) if err != nil { return nil, fmt.Errorf("opening %s: %w (is the usbshare driver installed?)", devicePath, err) } h := &DriverHandle{handle: handle} if err := h.claim(); err != nil { windows.CloseHandle(handle) return nil, err } return h, nil } func (h *DriverHandle) claim() error { out := make([]byte, unsafe.Sizeof(winDeviceInfo{})) var returned uint32 err := windows.DeviceIoControl(h.handle, ioctlClaim, nil, 0, &out[0], uint32(len(out)), &returned, nil) if err != nil { return fmt.Errorf("claiming device: %w", err) } h.info = *(*winDeviceInfo)(unsafe.Pointer(&out[0])) return nil } // Close releases the device and closes the handle. func (h *DriverHandle) Close() error { var returned uint32 windows.DeviceIoControl(h.handle, ioctlRelease, nil, 0, nil, 0, &returned, nil) return windows.CloseHandle(h.handle) } // Info returns the device information reported at claim time. func (h *DriverHandle) Info() winDeviceInfo { return h.info } // Descriptors reads the raw descriptor blob: device descriptor followed by // the configuration descriptors, the same layout Linux usbdevfs returns. It // is parsed by the same code on both platforms. func (h *DriverHandle) Descriptors() ([]byte, error) { // Ask with a generous buffer first; grow if the driver reports more. buf := make([]byte, 4096) var returned uint32 err := windows.DeviceIoControl(h.handle, ioctlGetDescriptors, nil, 0, &buf[0], uint32(len(buf)), &returned, nil) if err == windows.ERROR_INSUFFICIENT_BUFFER || err == windows.ERROR_MORE_DATA { buf = make([]byte, returned) err = windows.DeviceIoControl(h.handle, ioctlGetDescriptors, nil, 0, &buf[0], uint32(len(buf)), &returned, nil) } if err != nil { return nil, fmt.Errorf("reading descriptors: %w", err) } return buf[:returned], nil } // Transfer performs one USB transfer and blocks until it completes. // // For IN transfers data is the buffer to fill; for OUT transfers it holds the // payload to send. The returned count is how many bytes actually moved, which // matters for both directions. func (h *DriverHandle) Transfer(params *TransferParams) (int, error) { h.nextID++ header := winTransferHeader{ ID: h.nextID, EndpointAddress: params.EndpointAddress, Type: params.Type, Direction: params.Direction, BufferLength: uint32(len(params.Data)), Timeout: params.TimeoutMS, Setup: params.Setup, } // Input: header followed by the payload for OUT transfers. input := make([]byte, winTransferHeaderSize+len(params.Data)) marshalTransferHeader(input, &header) if params.Direction == winDirOut && len(params.Data) > 0 { copy(input[winTransferHeaderSize:], params.Data) } // Output: result header followed by the payload for IN transfers. output := make([]byte, winTransferResultSize+len(params.Data)) var returned uint32 err := windows.DeviceIoControl(h.handle, ioctlSubmit, &input[0], uint32(len(input)), &output[0], uint32(len(output)), &returned, nil) if err != nil { return 0, fmt.Errorf("submitting transfer: %w", err) } if returned < winTransferResultSize { return 0, fmt.Errorf("driver returned %d bytes, expected at least %d", returned, winTransferResultSize) } result := unmarshalTransferResult(output) if result.Status != 0 { return int(result.ActualLength), fmt.Errorf( "transfer failed: status 0x%08x, usbd 0x%08x", uint32(result.Status), result.UsbdStatus) } if params.Direction == winDirIn && result.ActualLength > 0 { n := int(result.ActualLength) if n > len(params.Data) { n = len(params.Data) } copy(params.Data, output[winTransferResultSize:winTransferResultSize+n]) } return int(result.ActualLength), nil } // TransferParams describes one transfer. type TransferParams struct { EndpointAddress uint8 Type uint8 Direction uint8 Data []byte TimeoutMS uint32 Setup [8]byte } // SetInterface selects an alternate setting through the driver, so the USB // stack re-opens the pipes and reserves bandwidth for isochronous endpoints. func (h *DriverHandle) SetInterface(iface, alt uint8) error { input := []byte{iface, alt} var returned uint32 err := windows.DeviceIoControl(h.handle, ioctlSetInterface, &input[0], uint32(len(input)), nil, 0, &returned, nil) if err != nil { return fmt.Errorf("setting interface %d to alt %d: %w", iface, alt, err) } return nil } // ClearHalt clears a stall condition on an endpoint. func (h *DriverHandle) ClearHalt(endpoint uint8) error { input := []byte{endpoint} var returned uint32 err := windows.DeviceIoControl(h.handle, ioctlClearHalt, &input[0], 1, nil, 0, &returned, nil) if err != nil { return fmt.Errorf("clearing halt on endpoint 0x%02x: %w", endpoint, err) } return nil } // Reset resets the device's port. func (h *DriverHandle) Reset() error { var returned uint32 err := windows.DeviceIoControl(h.handle, ioctlReset, nil, 0, nil, 0, &returned, nil) if err != nil { return fmt.Errorf("resetting device: %w", err) } return nil } // marshalTransferHeader writes the header in the driver's packed layout. // Done field by field rather than by casting a struct: Go inserts padding // that the packed C structure does not have. func marshalTransferHeader(buf []byte, h *winTransferHeader) { binary.LittleEndian.PutUint64(buf[0:8], h.ID) buf[8] = h.EndpointAddress buf[9] = h.Type buf[10] = h.Direction buf[11] = h.Reserved binary.LittleEndian.PutUint32(buf[12:16], h.BufferLength) binary.LittleEndian.PutUint32(buf[16:20], h.Timeout) copy(buf[20:28], h.Setup[:]) } func unmarshalTransferResult(buf []byte) winTransferResult { return winTransferResult{ ID: binary.LittleEndian.Uint64(buf[0:8]), Status: int32(binary.LittleEndian.Uint32(buf[8:12])), UsbdStatus: binary.LittleEndian.Uint32(buf[12:16]), ActualLength: binary.LittleEndian.Uint32(buf[16:20]), } }