//go:build linux package usbip import ( "testing" "github.com/duffy/usb-server/internal/usb" ) func newTestServer(eps map[uint8]usb.Endpoint) *Server { dev := &usb.Device{ BusID: "1-1", Endpoints: eps, } s := NewServer(dev) s.buildEndpointTypeMap() return s } // The composite case that broke HID: endpoint number 1 exists as bulk OUT // (0x01) and interrupt IN (0x81). Both must keep their own transfer type. func TestGetURBTypeSeparatesDirections(t *testing.T) { s := newTestServer(map[uint8]usb.Endpoint{ 0x01: {Address: 0x01, TransferType: usb.TransferTypeBulk}, 0x81: {Address: 0x81, TransferType: usb.TransferTypeInterrupt, Interval: 10}, 0x82: {Address: 0x82, TransferType: usb.TransferTypeIsochronous, Interval: 1}, }) tests := []struct { name string epAddr uint8 interval uint32 packets int32 want uint8 }{ {"bulk OUT endpoint 1", 0x01, 0, 0, usbdevfsTypeBulk}, {"interrupt IN endpoint 1", 0x81, 10, 0, usbdevfsTypeInterrupt}, {"isochronous IN endpoint 2", 0x82, 1, 8, usbdevfsTypeISO}, {"control endpoint 0", 0x00, 0, 0, usbdevfsTypeControl}, {"control endpoint 0 IN", 0x80, 0, 0, usbdevfsTypeControl}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := s.getURBType(tt.epAddr, tt.interval, tt.packets) if got != tt.want { t.Errorf("getURBType(0x%02x, interval=%d, packets=%d) = %s, want %s", tt.epAddr, tt.interval, tt.packets, urbTypeName[got], urbTypeName[tt.want]) } }) } } // An endpoint missing from the descriptor map must not default to bulk when // the request carries an interval: only periodic transfers have one, and // submitting an interrupt endpoint's URB as bulk is what the kernel rejects. func TestGetURBTypeFallsBackOnInterval(t *testing.T) { s := newTestServer(nil) if got := s.getURBType(0x83, 8, 0); got != usbdevfsTypeInterrupt { t.Errorf("unknown endpoint with interval=8: got %s, want INT", urbTypeName[got]) } if got := s.getURBType(0x02, 0, 0); got != usbdevfsTypeBulk { t.Errorf("unknown endpoint with interval=0: got %s, want BULK", urbTypeName[got]) } } // NumberOfPackets is authoritative for isochronous transfers: a webcam only // activates its ISO endpoints after SET_INTERFACE, so the descriptor map may // still describe the alternate-setting-0 view when the request arrives. func TestGetURBTypeISOWinsOverMap(t *testing.T) { s := newTestServer(map[uint8]usb.Endpoint{ 0x81: {Address: 0x81, TransferType: usb.TransferTypeBulk}, }) if got := s.getURBType(0x81, 1, 16); got != usbdevfsTypeISO { t.Errorf("packets=16 should force ISO, got %s", urbTypeName[got]) } } func TestBuildEndpointTypeMapFallsBackToInterfaces(t *testing.T) { dev := &usb.Device{ BusID: "1-1", Interfaces: []usb.Interface{{ Number: 0, Class: 0x03, Endpoints: []usb.Endpoint{ {Address: 0x81, TransferType: usb.TransferTypeInterrupt, Interval: 10}, }, }}, } s := NewServer(dev) s.buildEndpointTypeMap() if got := s.getURBType(0x81, 10, 0); got != usbdevfsTypeInterrupt { t.Errorf("sysfs fallback lost the interrupt type: got %s", urbTypeName[got]) } }