Config and interface description (#16)

* Add APIs for config and interface descriptors. Split out the common
parts of selecting a config descriptor from device desc and
selecting a setting descriptor from a config desc.

* Parallelize the few tests that actually can be parallelized safely.
Add comments where they can't. Note to self: it would be beneficial
to restructure the fakelibusb to index all properties of the lib
with the used context. That way a libusb implementation wouldn't need
to be referred via a shared variable.
This commit is contained in:
zagrodzki
2017-09-04 14:17:34 +02:00
committed by GitHub
parent cf19eb7001
commit f9aba6fab5
12 changed files with 175 additions and 53 deletions

View File

@@ -40,6 +40,8 @@ type ConfigDesc struct {
MaxPower Milliamperes
// Interfaces has a list of USB interfaces available in this configuration.
Interfaces []InterfaceDesc
iConfiguration int // index of a string descriptor describing this configuration
}
// String returns the human-readable description of the configuration descriptor.
@@ -47,6 +49,17 @@ func (c ConfigDesc) String() string {
return fmt.Sprintf("Configuration %d", c.Number)
}
func (c ConfigDesc) intfDesc(num, alt int) (*InterfaceSetting, error) {
if num < 0 || num >= len(c.Interfaces) {
return nil, fmt.Errorf("interface %d not found, available interfaces 0..%d", num, len(c.Interfaces)-1)
}
ifInfo := c.Interfaces[num]
if alt < 0 || alt >= len(ifInfo.AltSettings) {
return nil, fmt.Errorf("alternate setting %d not found for %s, available alt settings 0..%d", alt, ifInfo, len(ifInfo.AltSettings)-1)
}
return &ifInfo.AltSettings[alt], nil
}
// Config represents a USB device set to use a particular configuration.
// Only one Config of a particular device can be used at any one time.
// To access device endpoints, claim an interface and it's alternate
@@ -94,12 +107,10 @@ func (c *Config) Interface(num, alt int) (*Interface, error) {
if c.dev == nil {
return nil, fmt.Errorf("Interface(%d, %d) called on %s after Close", num, alt, c)
}
if num < 0 || num >= len(c.Desc.Interfaces) {
return nil, fmt.Errorf("interface %d not found in %s, available interfaces 0..%d", num, c, len(c.Desc.Interfaces)-1)
}
ifInfo := c.Desc.Interfaces[num]
if alt < 0 || alt >= len(ifInfo.AltSettings) {
return nil, fmt.Errorf("alternate setting %d not found for %s in %s, available alt settings 0..%d", alt, ifInfo, c, len(ifInfo.AltSettings)-1)
altInfo, err := c.Desc.intfDesc(num, alt)
if err != nil {
return nil, fmt.Errorf("in %s: %v", err)
}
c.mu.Lock()
@@ -120,7 +131,7 @@ func (c *Config) Interface(num, alt int) (*Interface, error) {
c.claimed[num] = true
return &Interface{
Setting: ifInfo.AltSettings[alt],
Setting: *altInfo,
config: c,
}, nil
}

View File

@@ -56,6 +56,23 @@ func (d *DeviceDesc) String() string {
return fmt.Sprintf("%d.%d: %s:%s (available configs: %v)", d.Bus, d.Address, d.Vendor, d.Product, d.sortedConfigIds())
}
func (d *DeviceDesc) sortedConfigIds() []int {
var cfgs []int
for c := range d.Configs {
cfgs = append(cfgs, c)
}
sort.Ints(cfgs)
return cfgs
}
func (d *DeviceDesc) cfgDesc(cfgNum int) (*ConfigDesc, error) {
desc, ok := d.Configs[cfgNum]
if !ok {
return nil, fmt.Errorf("configuration id %d not found in the descriptor of the device. Available config ids: %v", cfgNum, d.sortedConfigIds())
}
return &desc, nil
}
// Device represents an opened USB device.
// Device allows sending USB control commands through the Command() method.
// For data transfers select a device configuration through a call to
@@ -77,15 +94,6 @@ type Device struct {
autodetach bool
}
func (d *DeviceDesc) sortedConfigIds() []int {
var cfgs []int
for c := range d.Configs {
cfgs = append(cfgs, c)
}
sort.Ints(cfgs)
return cfgs
}
// String represents a human readable representation of the device.
func (d *Device) String() string {
return fmt.Sprintf("vid=%s,pid=%s,bus=%d,addr=%d", d.Desc.Vendor, d.Desc.Product, d.Desc.Bus, d.Desc.Address)
@@ -126,12 +134,12 @@ func (d *Device) Config(cfgNum int) (*Config, error) {
if d.handle == nil {
return nil, fmt.Errorf("Config(%d) called on %s after Close", cfgNum, d)
}
desc, ok := d.Desc.Configs[cfgNum]
if !ok {
return nil, fmt.Errorf("configuration id %d not found in the descriptor of the device %s. Available config ids: %v", cfgNum, d, d.Desc.sortedConfigIds())
desc, err := d.Desc.cfgDesc(cfgNum)
if err != nil {
return nil, fmt.Errorf("device %s: %v", d, err)
}
cfg := &Config{
Desc: desc,
Desc: *desc,
dev: d,
claimed: make(map[int]bool),
}
@@ -212,6 +220,10 @@ func (d *Device) GetStringDescriptor(descIndex int) (string, error) {
if d.handle == nil {
return "", fmt.Errorf("GetStringDescriptor(%d) called on %s after Close", descIndex, d)
}
// string descriptor index value of 0 indicates no string descriptor.
if descIndex == 0 {
return "", nil
}
return libusb.getStringDesc(d.handle, descIndex)
}
@@ -233,6 +245,31 @@ func (d *Device) SerialNumber() (string, error) {
return d.GetStringDescriptor(d.Desc.iSerialNumber)
}
// ConfigDescription returns the description of the selected device
// configuration. GetStringDescriptor's string conversion rules apply.
func (d *Device) ConfigDescription(cfg int) (string, error) {
c, err := d.Desc.cfgDesc(cfg)
if err != nil {
return "", fmt.Errorf("%s: %v", d, err)
}
return d.GetStringDescriptor(c.iConfiguration)
}
// InterfaceDescription returns the description of the selected interface and
// its alternate setting in a selected configuration. GetStringDescriptor's
// string conversion rules apply.
func (d *Device) InterfaceDescription(cfgNum, intfNum, altNum int) (string, error) {
cfg, err := d.Desc.cfgDesc(cfgNum)
if err != nil {
return "", fmt.Errorf("%s: %v", d, err)
}
alt, err := cfg.intfDesc(intfNum, altNum)
if err != nil {
return "", fmt.Errorf("%s, configuration %d: %v", d, cfgNum, err)
}
return d.GetStringDescriptor(alt.iInterface)
}
// SetAutoDetach enables/disables automatic kernel driver detachment.
// When autodetach is enabled gousb will automatically detach the kernel driver
// on the interface and reattach it when releasing the interface.

View File

@@ -21,6 +21,7 @@ import (
)
func TestClaimAndRelease(t *testing.T) {
// Can't be parallelized, newFakeLibusb modifies a shared global state.
_, done := newFakeLibusb()
defer done()
@@ -44,26 +45,45 @@ func TestClaimAndRelease(t *testing.T) {
t.Fatalf("OpenDeviceWithVIDPID(0x8888, 0x0002): %v", err)
}
mfg, err := dev.Manufacturer()
if err != nil {
t.Errorf("%s.Manufacturer(): %v", dev, err)
if mfg, err := dev.Manufacturer(); err != nil {
t.Errorf("%s.Manufacturer(): error %v", dev, err)
} else if want := "ACME Industries"; mfg != want {
t.Errorf("%s.Manufacturer(): %q, want %q", dev, mfg, want)
}
if mfg != "ACME Industries" {
t.Errorf("%s.Manufacturer(): %q", dev, mfg)
if prod, err := dev.Product(); err != nil {
t.Errorf("%s.Product(): error %v", dev, err)
} else if want := "Fidgety Gadget"; prod != want {
t.Errorf("%s.Product(): %q, want %q", dev, prod, want)
}
prod, err := dev.Product()
if err != nil {
t.Errorf("%s.Product(): %v", dev, err)
if sn, err := dev.SerialNumber(); err != nil {
t.Errorf("%s.SerialNumber(): error %v", dev, err)
} else if want := "01234567"; sn != want {
t.Errorf("%s.SerialNumber(): %q, want %q", dev, sn, want)
}
if prod != "Fidgety Gadget" {
t.Errorf("%s.Product(): %q", dev, prod)
if got, err := dev.ConfigDescription(1); err != nil {
t.Errorf("%s.ConfigDescription(1): %v", dev, err)
} else if want := "Weird configuration"; got != want {
t.Errorf("%s.ConfigDescription(1): %q, want %q", dev, got, want)
}
sn, err := dev.SerialNumber()
if err != nil {
t.Errorf("%s.SerialNumber(): %v", dev, err)
if got, err := dev.ConfigDescription(2); err == nil {
t.Errorf("%s.ConfigDescription(2): %q, want error", dev, got)
}
if sn != "01234567" {
t.Errorf("%s.SerialNumber(): %q", dev, sn)
for _, tc := range []struct {
intf, alt int
want string
}{
{0, 0, "Boring setting"},
{1, 0, "Fast streaming"},
{1, 1, "Slower streaming"},
{1, 2, ""},
} {
if got, err := dev.InterfaceDescription(1, tc.intf, tc.alt); err != nil {
t.Errorf("%s.InterfaceDescription(1, %d, %d): %v", dev, tc.intf, tc.alt, err)
} else if got != tc.want {
t.Errorf("%s.InterfaceDescription(1, %d, %d): %q, want %q", dev, tc.intf, tc.alt, got, tc.want)
}
}
if err = dev.SetAutoDetach(true); err != nil {
@@ -156,6 +176,38 @@ func TestClaimAndRelease(t *testing.T) {
}
}
func TestInterfaceDescriptionError(t *testing.T) {
// Can't be parallelized, newFakeLibusb modifies a shared global state.
_, done := newFakeLibusb()
defer done()
for _, tc := range []struct {
name string
cfg, intf, alt int
}{
{"no config", 2, 1, 1},
{"no interface", 1, 3, 1},
{"no alt setting", 1, 1, 5},
} {
t.Run(tc.name, func(t *testing.T) {
// Can't be parallelized, depends on the shared global state set before the loop.
c := NewContext()
defer c.Close()
dev, err := c.OpenDeviceWithVIDPID(0x8888, 0x0002)
if dev == nil {
t.Fatal("OpenDeviceWithVIDPID(0x8888, 0x0002): got nil device, need non-nil")
}
defer dev.Close()
if err != nil {
t.Fatalf("OpenDeviceWithVIDPID(0x8888, 0x0002): %v", err)
}
if desc, err := dev.InterfaceDescription(tc.cfg, tc.intf, tc.alt); err == nil {
t.Errorf("%s.InterfaceDescriptor(%d, %d, %d): %q, want error", dev, tc.cfg, tc.intf, tc.alt, desc)
}
})
}
}
type failDetachLib struct {
*fakeLibusb
}
@@ -168,6 +220,7 @@ func (*failDetachLib) detachKernelDriver(h *libusbDevHandle, i uint8) error {
}
func TestAutoDetachFailure(t *testing.T) {
// Can't be parallelized, newFakeLibusb modifies a shared global state.
fake, done := newFakeLibusb()
defer done()
libusb = &failDetachLib{fake}

View File

@@ -17,6 +17,7 @@ package gousb
import "testing"
func TestEndpointReadStream(t *testing.T) {
// Can't be parallelized, newFakeLibusb modifies a shared global state.
lib, done := newFakeLibusb()
defer done()

View File

@@ -20,6 +20,7 @@ import (
)
func TestEndpoint(t *testing.T) {
// Can't be parallelized, newFakeLibusb modifies a shared global state.
lib, done := newFakeLibusb()
defer done()
@@ -116,6 +117,7 @@ func TestEndpoint(t *testing.T) {
}
func TestEndpointInfo(t *testing.T) {
t.Parallel()
for _, tc := range []struct {
ep EndpointDesc
want string
@@ -161,8 +163,7 @@ func TestEndpointInfo(t *testing.T) {
}
func TestEndpointInOut(t *testing.T) {
defer func(i libusbIntf) { libusb = i }(libusb)
// Can't be parallelized, newFakeLibusb modifies a shared global state.
lib, done := newFakeLibusb()
defer done()
@@ -242,8 +243,7 @@ func TestEndpointInOut(t *testing.T) {
}
func TestSameEndpointNumberInOut(t *testing.T) {
defer func(i libusbIntf) { libusb = i }(libusb)
// Can't be parallelized, newFakeLibusb modifies a shared global state.
_, done := newFakeLibusb()
defer done()

View File

@@ -78,14 +78,16 @@ var fakeDevices = []fakeDevice{
Product: ID(0x0002),
Protocol: 255,
Configs: map[int]ConfigDesc{1: {
Number: 1,
MaxPower: Milliamperes(100),
Number: 1,
MaxPower: Milliamperes(100),
iConfiguration: 5,
Interfaces: []InterfaceDesc{{
Number: 0,
AltSettings: []InterfaceSetting{{
Number: 0,
Alternate: 0,
Class: ClassVendorSpec,
Number: 0,
Alternate: 0,
Class: ClassVendorSpec,
iInterface: 6,
}},
}, {
Number: 1,
@@ -111,6 +113,7 @@ var fakeDevices = []fakeDevice{
UsageType: IsoUsageTypeData,
},
},
iInterface: 7,
}, {
Number: 1,
Alternate: 1,
@@ -131,6 +134,7 @@ var fakeDevices = []fakeDevice{
TransferType: TransferTypeIsochronous,
},
},
iInterface: 8,
}, {
Number: 1,
Alternate: 2,
@@ -162,6 +166,10 @@ var fakeDevices = []fakeDevice{
1: "ACME Industries",
2: "Fidgety Gadget",
3: "01234567",
5: "Weird configuration",
6: "Boring setting",
7: "Fast streaming",
8: "Slower streaming",
},
},
// Bus 001 Device 003: ID 9999:0002

View File

@@ -52,6 +52,8 @@ type InterfaceSetting struct {
// Endpoints enumerates the endpoints available on this interface with
// this alternate setting.
Endpoints map[EndpointAddress]EndpointDesc
iInterface int // index of a string descriptor describing this interface.
}
func (a InterfaceSetting) sortedEndpointIds() []string {

View File

@@ -250,10 +250,11 @@ func (libusbImpl) getDeviceDesc(d *libusbDevice) (*DeviceDesc, error) {
return nil, err
}
c := ConfigDesc{
Number: int(cfg.bConfigurationValue),
SelfPowered: (cfg.bmAttributes & selfPoweredMask) != 0,
RemoteWakeup: (cfg.bmAttributes & remoteWakeupMask) != 0,
MaxPower: 2 * Milliamperes(cfg.MaxPower),
Number: int(cfg.bConfigurationValue),
SelfPowered: (cfg.bmAttributes & selfPoweredMask) != 0,
RemoteWakeup: (cfg.bmAttributes & remoteWakeupMask) != 0,
MaxPower: 2 * Milliamperes(cfg.MaxPower),
iConfiguration: int(cfg.iConfiguration),
}
// at GenX speeds MaxPower is expressed in units of 8mA, not 2mA.
if dev.Speed == SpeedSuper {
@@ -281,11 +282,12 @@ func (libusbImpl) getDeviceDesc(d *libusbDevice) (*DeviceDesc, error) {
descs := make([]InterfaceSetting, 0, len(alts))
for altNum, alt := range alts {
i := InterfaceSetting{
Number: int(alt.bInterfaceNumber),
Alternate: int(alt.bAlternateSetting),
Class: Class(alt.bInterfaceClass),
SubClass: Class(alt.bInterfaceSubClass),
Protocol: Protocol(alt.bInterfaceProtocol),
Number: int(alt.bInterfaceNumber),
Alternate: int(alt.bAlternateSetting),
Class: Class(alt.bInterfaceClass),
SubClass: Class(alt.bInterfaceSubClass),
Protocol: Protocol(alt.bInterfaceProtocol),
iInterface: int(alt.iInterface),
}
if ifNum != i.Number {
return nil, fmt.Errorf("config %d interface at index %d has number %d, USB standard states they should be identical", c.Number, ifNum, i.Number)

View File

@@ -20,6 +20,7 @@ import (
)
func TestBCD(t *testing.T) {
t.Parallel()
tests := []struct {
major, minor uint8
bcd BCD

View File

@@ -106,6 +106,7 @@ func (r readRes) String() string {
}
func TestTransferReadStream(t *testing.T) {
t.Parallel()
for tcNum, tc := range []struct {
desc string
closeBefore int
@@ -211,7 +212,9 @@ func TestTransferReadStream(t *testing.T) {
},
},
} {
tcNum, tc := tcNum, tc // t.Parallel will delay the execution of the test, save the iteration values.
t.Run(strconv.Itoa(tcNum), func(t *testing.T) {
t.Parallel()
t.Logf("Case %d: %s", tcNum, tc.desc)
ftt := make([]*fakeStreamTransfer, len(tc.transfers))
tt := make([]transferIntf, len(tc.transfers))

View File

@@ -20,6 +20,7 @@ import (
)
func TestNewTransfer(t *testing.T) {
// Can't be parallelized, newFakeLibusb modifies a shared global state.
_, done := newFakeLibusb()
defer done()
@@ -70,6 +71,7 @@ func TestNewTransfer(t *testing.T) {
}
func TestTransferProtocol(t *testing.T) {
// Can't be parallelized, newFakeLibusb modifies a shared global state.
f, done := newFakeLibusb()
defer done()

View File

@@ -18,6 +18,7 @@ package gousb
import "testing"
func TestOPenDevices(t *testing.T) {
// Can't be parallelized, newFakeLibusb modifies a shared global state.
_, done := newFakeLibusb()
defer done()
@@ -54,6 +55,7 @@ func TestOPenDevices(t *testing.T) {
}
func TestOpenDeviceWithVIDPID(t *testing.T) {
// Can't be parallelized, newFakeLibusb modifies a shared global state.
_, done := newFakeLibusb()
defer done()