package usb import ( "encoding/binary" "fmt" ) // USB descriptor types const ( DescTypeDevice = 0x01 DescTypeConfiguration = 0x02 DescTypeInterface = 0x04 DescTypeEndpoint = 0x05 ) // ParsedDescriptors holds everything we extract from a device's raw // descriptor blob (device descriptor followed by all configuration // descriptors, as returned by reading a usbdevfs device file). type ParsedDescriptors struct { VendorID uint16 ProductID uint16 BcdDevice uint16 DeviceClass uint8 DeviceSubClass uint8 DeviceProtocol uint8 NumConfigs uint8 // Configs holds every configuration, each with every interface // alternate setting and its endpoints. Configs []ConfigDescriptor } // ConfigDescriptor is one USB configuration type ConfigDescriptor struct { Value uint8 // bConfigurationValue Interfaces []Interface // every alternate setting, in descriptor order } // ParseDescriptors parses a raw descriptor blob: an 18-byte device // descriptor followed by one or more complete configuration descriptors. // // Reading a usbdevfs file (/dev/bus/usb/BBB/DDD) from offset 0 yields // exactly this layout, which is the only way to see interface alternate // settings — sysfs only exposes the currently active one. func ParseDescriptors(data []byte) (*ParsedDescriptors, error) { if len(data) < 18 { return nil, fmt.Errorf("descriptor blob too short: %d bytes", len(data)) } if data[1] != DescTypeDevice { return nil, fmt.Errorf("first descriptor is type 0x%02x, expected device (0x01)", data[1]) } pd := &ParsedDescriptors{ DeviceClass: data[4], DeviceSubClass: data[5], DeviceProtocol: data[6], VendorID: binary.LittleEndian.Uint16(data[8:10]), ProductID: binary.LittleEndian.Uint16(data[10:12]), BcdDevice: binary.LittleEndian.Uint16(data[12:14]), NumConfigs: data[17], } // Walk the remaining descriptors. Configuration descriptors start a new // config; interface descriptors start a new alternate setting; endpoint // descriptors attach to the most recent interface. Class-specific // descriptors (HID, UVC, audio) are skipped by their bLength. pos := int(data[0]) // skip the device descriptor using its own bLength if pos < 18 { pos = 18 } var curConfig *ConfigDescriptor var curIface *Interface for pos+2 <= len(data) { bLength := int(data[pos]) bType := data[pos+1] // A zero-length descriptor would loop forever; a descriptor running // past the end of the blob means the device returned garbage. if bLength < 2 || pos+bLength > len(data) { break } switch bType { case DescTypeConfiguration: if bLength >= 9 { pd.Configs = append(pd.Configs, ConfigDescriptor{Value: data[pos+5]}) curConfig = &pd.Configs[len(pd.Configs)-1] curIface = nil } case DescTypeInterface: if bLength >= 9 && curConfig != nil { curConfig.Interfaces = append(curConfig.Interfaces, Interface{ Number: data[pos+2], AltSetting: data[pos+3], Class: data[pos+5], SubClass: data[pos+6], Protocol: data[pos+7], }) curIface = &curConfig.Interfaces[len(curConfig.Interfaces)-1] } case DescTypeEndpoint: if bLength >= 7 && curIface != nil { curIface.Endpoints = append(curIface.Endpoints, Endpoint{ Address: data[pos+2], TransferType: data[pos+3] & 0x03, MaxPacketSize: binary.LittleEndian.Uint16(data[pos+4 : pos+6]), Interval: data[pos+6], }) } } pos += bLength } if len(pd.Configs) == 0 { return nil, fmt.Errorf("no configuration descriptor found") } return pd, nil } // FindConfig returns the configuration with the given bConfigurationValue, // or nil if the device has no such configuration. func (pd *ParsedDescriptors) FindConfig(value uint8) *ConfigDescriptor { for i := range pd.Configs { if pd.Configs[i].Value == value { return &pd.Configs[i] } } return nil } // AllEndpoints returns every endpoint across every alternate setting of the // given configuration, keyed by full bEndpointAddress (direction bit // included). Endpoints only present in a non-zero alternate setting — the // isochronous endpoints of webcams, for example — are included, which is // what makes the endpoint type map correct after a SET_INTERFACE. func (c *ConfigDescriptor) AllEndpoints() map[uint8]Endpoint { eps := make(map[uint8]Endpoint) for _, iface := range c.Interfaces { for _, ep := range iface.Endpoints { // Alternate settings reuse addresses with identical transfer // types in practice; keep the first one we see so alt 0 wins. if _, seen := eps[ep.Address]; !seen { eps[ep.Address] = ep } } } return eps } // ActiveInterfaces returns one Interface per interface number, using // alternate setting 0 — the set of interfaces that must be claimed. func (c *ConfigDescriptor) ActiveInterfaces() []Interface { var result []Interface seen := make(map[uint8]bool) for _, iface := range c.Interfaces { if iface.AltSetting != 0 || seen[iface.Number] { continue } seen[iface.Number] = true result = append(result, iface) } return result }