diff --git a/descriptors.go b/descriptors.go new file mode 100644 index 0000000..48fff4c --- /dev/null +++ b/descriptors.go @@ -0,0 +1,107 @@ +// Copyright 2017 the gousb Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gousb + +import ( + "bytes" + "encoding/binary" + "fmt" +) + +// Descriptor structs based on USB 2.0 spec, section 9.6 + +const ( + deviceDescLen = 18 + configDescLen = 9 + intfDescLen = 9 + epDescLen = 7 +) + +// decoded device descriptor, 18 bytes +type usbDeviceDescriptor struct { + BLength uint8 // 0 + BDescriptorType uint8 // 1 + BCDUSB uint16 // 2:3 + BDeviceClass uint8 // 4 + BDeviceSubClass uint8 // 5 + BDeviceProtocol uint8 // 6 + BMaxPacketSize0 uint8 // 7 + IDVendor uint16 // 8:9 + IDProduct uint16 // 10:11 + BCDDevice uint16 // 12:13 + IManufacturer uint8 // 14 + IProduct uint8 // 15 + ISerialNumber uint8 // 16 + BNumConfigurations uint8 // 17 +} + +func deviceDescFromBytes(descBytes []byte) (*DeviceDesc, error) { + if len(descBytes) < deviceDescLen { + return nil, fmt.Errorf("device descriptor is %d bytes, got only %d bytes", descBytes, len(descBytes)) + } + d := &usbDeviceDescriptor{} + b := bytes.NewReader(descBytes[:deviceDescLen]) + err := binary.Read(b, binary.LittleEndian, d) + if err != nil { + return nil, fmt.Errorf("failed to build the device descriptor: %v", err) + } + dev := &DeviceDesc{ + Spec: BCD(d.BCDUSB), + Class: Class(d.BDeviceClass), + SubClass: Class(d.BDeviceSubClass), + Protocol: Protocol(d.BDeviceProtocol), + MaxControlPacketSize: int(d.BMaxPacketSize0), + Vendor: ID(d.IDVendor), + Product: ID(d.IDProduct), + Device: BCD(d.BCDDevice), + Configs: make(map[int]ConfigDesc, d.BNumConfigurations), + } + return dev, nil +} + +// decoded configuration descriptor, 9 bytes followed by bNumInterfaces interface descriptors +type usbConfigDescriptor struct { + bLength uint8 // 0 + bDescriptorType uint8 // 1 + wTotalLength uint16 // 2:3 + bNumInterfaces uint8 // 4 + bConfigurationValue uint8 // 5 + iConfiguration uint8 // 6 + bmAttributes uint8 // 7 + bMaxPower uint8 // 8 +} + +// decoded interface descriptor, 9 bytes followed by bNumEndpoints endpoint descriptors. +type usbInterfaceDescriptor struct { + bLength uint8 // 0 + bDescriptorType uint8 // 1 + bInterfaceNumber uint8 // 2 + bAlternateSetting uint8 // 3 + bNumEndpoints uint8 // 4 + bInterfaceClass uint8 // 5 + bInterfaceSubClass uint8 // 6 + bInterfaceProtocol uint8 // 7 + iInterface uint8 // 8 +} + +// decoded endpoint descriptor, 7 bytes. +type usbEndpointDescriptor struct { + bLength uint8 // 0 + bDescriptorType uint8 // 1 + bEndpointAddress uint8 // 2 + bmAttributes uint8 // 3 + wMaxPacketSize uint16 // 4:5 + bInterval uint8 // 6 +} diff --git a/descriptors_test.go b/descriptors_test.go new file mode 100644 index 0000000..2f26d10 --- /dev/null +++ b/descriptors_test.go @@ -0,0 +1,76 @@ +// Copyright 2017 the gousb Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gousb + +import ( + "bytes" + "encoding/hex" + "fmt" + "io/ioutil" + "os" + "reflect" + "testing" +) + +func bytesFromHexFile(path string) ([]byte, error) { + f, err := os.Open(path) + if err != nil { + return nil, fmt.Errorf("Open(): %v", err) + } + defer f.Close() + hexData, err := ioutil.ReadAll(f) + if err != nil { + return nil, fmt.Errorf("ReadAll(): %v", err) + } + hexData = bytes.TrimSpace(hexData) + if len(hexData)%2 == 1 { + return nil, fmt.Errorf("hex data file has %d characters, expected an even number", len(hexData)) + } + data := make([]byte, len(hexData)/2) + _, err = hex.Decode(data, hexData) + if err != nil { + return nil, fmt.Errorf("failed to decode hex data: %v", err) + } + return data, nil +} + +func TestDeviceDescriptor(t *testing.T) { + path := "testdata/mouse_device_desc.hex" + descData, err := bytesFromHexFile(path) + if err != nil { + t.Fatalf("loading data from %q failed: %v", path, err) + } + + got, err := deviceDescFromBytes(descData) + if err != nil { + t.Fatalf("failed to decode device descriptor from %q: %v", path, err) + } + + // based on device descriptor as presented in mouse_lsusb.txt + want := &DeviceDesc{ + Spec: Version(2, 0), + Class: ClassPerInterface, + SubClass: ClassPerInterface, + Protocol: Protocol(0), + MaxControlPacketSize: 32, + Vendor: ID(0x046d), + Product: ID(0xc526), + Device: Version(5, 0), + Configs: make(map[int]ConfigDesc), + } + if !reflect.DeepEqual(got, want) { + t.Errorf("Got descriptor: %+v\nwant: %+v", got, want) + } +} diff --git a/device.go b/device.go index a4f2662..853b2a2 100644 --- a/device.go +++ b/device.go @@ -25,8 +25,9 @@ import ( // DeviceDesc is a representation of a USB device descriptor. type DeviceDesc struct { // Bus information - Bus int // The bus on which the device was detected - Address int // The address of the device on the bus + Bus int // The bus on which the device was detected + Address int // The address of the device on the bus + Speed Speed // The negotiated operating speed for the device // Version information Spec BCD // USB Specification Release Number @@ -37,9 +38,10 @@ type DeviceDesc struct { Product ID // The Product identifier // Protocol information - Class Class // The class of this device - SubClass Class // The sub-class (within the class) of this device - Protocol Protocol // The protocol (within the sub-class) of this device + Class Class // The class of this device + SubClass Class // The sub-class (within the class) of this device + Protocol Protocol // The protocol (within the sub-class) of this device + MaxControlPacketSize int // Maximum size of the control transfer // Configuration information Configs map[int]ConfigDesc