Browse Source

accounts/usbwallet: support webusb for Trezor wallets

Guillaume Ballet 6 years ago
parent
commit
4799b5abd4

+ 19 - 3
accounts/usbwallet/hub.go

@@ -89,6 +89,12 @@ func NewTrezorHub() (*Hub, error) {
 	return newHub(TrezorScheme, 0x534c, []uint16{0x0001 /* Trezor 1 */}, 0xff00, 0, newTrezorDriver)
 	return newHub(TrezorScheme, 0x534c, []uint16{0x0001 /* Trezor 1 */}, 0xff00, 0, newTrezorDriver)
 }
 }
 
 
+// NewWebUSBTrezorHub creates a new hardware wallet manager for Trezor devices with
+// firmware version > 1.8.0
+func NewWebUSBTrezorHub() (*Hub, error) {
+	return newHub(TrezorScheme, 0x1209, []uint16{0x53c1 /* Trezor 1 WebUSB */}, 0, 0, newTrezorDriver)
+}
+
 // newHub creates a new hardware wallet manager for generic USB devices.
 // newHub creates a new hardware wallet manager for generic USB devices.
 func newHub(scheme string, vendorID uint16, productIDs []uint16, usageID uint16, endpointID int, makeDriver func(log.Logger) driver) (*Hub, error) {
 func newHub(scheme string, vendorID uint16, productIDs []uint16, usageID uint16, endpointID int, makeDriver func(log.Logger) driver) (*Hub, error) {
 	if !hid.Supported() {
 	if !hid.Supported() {
@@ -148,9 +154,19 @@ func (hub *Hub) refreshWallets() {
 			return
 			return
 		}
 		}
 	}
 	}
-	for _, info := range hid.Enumerate(hub.vendorID, 0) {
+	infos, err := hid.Enumerate(hub.vendorID, 0)
+	if err != nil {
+		if runtime.GOOS == "linux" {
+			// See rationale before the enumeration why this is needed and only on Linux.
+			hub.commsLock.Unlock()
+		}
+		log.Error("error enumerating USB enumeration: ", "code", err)
+		return
+	}
+	for _, info := range infos {
 		for _, id := range hub.productIDs {
 		for _, id := range hub.productIDs {
-			if info.ProductID == id && (info.UsagePage == hub.usageID || info.Interface == hub.endpointID) {
+			_, pid, endpoint, _ /* FIXME usageID */ := info.IDs()
+			if pid == id && ( /* FIXME usageID == hub.usageID || */ endpoint == hub.endpointID) {
 				devices = append(devices, info)
 				devices = append(devices, info)
 				break
 				break
 			}
 			}
@@ -169,7 +185,7 @@ func (hub *Hub) refreshWallets() {
 	)
 	)
 
 
 	for _, device := range devices {
 	for _, device := range devices {
-		url := accounts.URL{Scheme: hub.scheme, Path: device.Path}
+		url := accounts.URL{Scheme: hub.scheme, Path: device.GetPath()}
 
 
 		// Drop wallets in front of the next device or those that failed for some reason
 		// Drop wallets in front of the next device or those that failed for some reason
 		for len(hub.wallets) > 0 {
 		for len(hub.wallets) > 0 {

+ 2 - 0
accounts/usbwallet/trezor.go

@@ -36,6 +36,8 @@ import (
 	"github.com/golang/protobuf/proto"
 	"github.com/golang/protobuf/proto"
 )
 )
 
 
+var ErrInvalidDeviceType = errors.New("trezor: invalid device type")
+
 // ErrTrezorPINNeeded is returned if opening the trezor requires a PIN code. In
 // ErrTrezorPINNeeded is returned if opening the trezor requires a PIN code. In
 // this case, the calling application should display a pinpad and send back the
 // this case, the calling application should display a pinpad and send back the
 // encoded passphrase.
 // encoded passphrase.

+ 1 - 1
accounts/usbwallet/wallet.go

@@ -78,7 +78,7 @@ type wallet struct {
 	url    *accounts.URL // Textual URL uniquely identifying this wallet
 	url    *accounts.URL // Textual URL uniquely identifying this wallet
 
 
 	info   hid.DeviceInfo // Known USB device infos about the wallet
 	info   hid.DeviceInfo // Known USB device infos about the wallet
-	device *hid.Device    // USB device advertising itself as a hardware wallet
+	device hid.Device     // USB device advertising itself as a hardware wallet
 
 
 	accounts []accounts.Account                         // List of derive accounts pinned on the hardware wallet
 	accounts []accounts.Account                         // List of derive accounts pinned on the hardware wallet
 	paths    map[common.Address]accounts.DerivationPath // Known derivation paths for signing operations
 	paths    map[common.Address]accounts.DerivationPath // Known derivation paths for signing operations

+ 6 - 0
node/config.go

@@ -507,6 +507,12 @@ func makeAccountManager(conf *Config) (*accounts.Manager, string, error) {
 			} else {
 			} else {
 				backends = append(backends, trezorhub)
 				backends = append(backends, trezorhub)
 			}
 			}
+			// Start a USB hub for Trezor hardware wallets (WebUSB version)
+			if trezorhub, err := usbwallet.NewWebUSBTrezorHub(); err != nil {
+				log.Warn(fmt.Sprintf("Failed to start Trezor hub, disabling: %v", err))
+			} else {
+				backends = append(backends, trezorhub)
+			}
 		}
 		}
 		if len(conf.SmartCardDaemonPath) > 0 {
 		if len(conf.SmartCardDaemonPath) > 0 {
 			// Start a smart card hub
 			// Start a smart card hub

+ 7 - 0
signer/core/api.go

@@ -151,6 +151,13 @@ func StartClefAccountManager(ksLocation string, nousb, lightKDF bool) *accounts.
 			backends = append(backends, trezorhub)
 			backends = append(backends, trezorhub)
 			log.Debug("Trezor support enabled")
 			log.Debug("Trezor support enabled")
 		}
 		}
+		// Start a USB hub for Trezor hardware wallets (WebUSB version)
+		if trezorhub, err := usbwallet.NewWebUSBTrezorHub(); err != nil {
+			log.Warn(fmt.Sprintf("Failed to start Trezor hub, disabling: %v", err))
+		} else {
+			backends = append(backends, trezorhub)
+			log.Debug("Trezor support enabled")
+		}
 	}
 	}
 	// Clef doesn't allow insecure http account unlock.
 	// Clef doesn't allow insecure http account unlock.
 	return accounts.NewManager(&accounts.Config{InsecureUnlockAllowed: false}, backends...)
 	return accounts.NewManager(&accounts.Config{InsecureUnlockAllowed: false}, backends...)

+ 54 - 0
vendor/github.com/karalabe/hid/generic.go

@@ -0,0 +1,54 @@
+// hid - Gopher Interface Devices (USB HID)
+// Copyright (c) 2019 Péter Szilágyi, Guillaume Ballet. All rights reserved.
+
+package hid
+
+import (
+	"C"
+)
+
+type GenericEndpointDirection uint8
+
+// List of endpoint direction types
+const (
+	GenericEndpointDirectionOut = 0x00
+	GenericEndpointDirectionIn  = 0x80
+)
+
+// List of endpoint attributes
+const (
+	GenericEndpointAttributeInterrupt = 3
+)
+
+// GenericEndpoint represents a USB endpoint
+type GenericEndpoint struct {
+	Address    uint8
+	Direction  GenericEndpointDirection
+	Attributes uint8
+}
+
+type GenericDeviceInfo struct {
+	Path      string // Platform-specific device path
+	VendorID  uint16 // Device Vendor ID
+	ProductID uint16 // Device Product ID
+
+	device *GenericDevice
+
+	Interface int
+
+	Endpoints []GenericEndpoint
+}
+
+func (gdi *GenericDeviceInfo) Type() DeviceType {
+	return DeviceTypeGeneric
+}
+
+// Platform-specific device path
+func (gdi *GenericDeviceInfo) GetPath() string {
+	return gdi.Path
+}
+
+// IDs returns the vendor and product IDs for the device
+func (gdi *GenericDeviceInfo) IDs() (uint16, uint16, int, uint16) {
+	return gdi.VendorID, gdi.ProductID, gdi.Interface, 0
+}

+ 17 - 2
vendor/github.com/karalabe/hid/hid.go

@@ -17,8 +17,8 @@ var ErrDeviceClosed = errors.New("hid: device closed")
 // operating system is not supported by the library.
 // operating system is not supported by the library.
 var ErrUnsupportedPlatform = errors.New("hid: unsupported platform")
 var ErrUnsupportedPlatform = errors.New("hid: unsupported platform")
 
 
-// DeviceInfo is a hidapi info structure.
-type DeviceInfo struct {
+// HidDeviceInfo is a hidapi info structure.
+type HidDeviceInfo struct {
 	Path         string // Platform-specific device path
 	Path         string // Platform-specific device path
 	VendorID     uint16 // Device Vendor ID
 	VendorID     uint16 // Device Vendor ID
 	ProductID    uint16 // Device Product ID
 	ProductID    uint16 // Device Product ID
@@ -35,3 +35,18 @@ type DeviceInfo struct {
 	// only if the device contains more than one interface.
 	// only if the device contains more than one interface.
 	Interface int
 	Interface int
 }
 }
+
+// GetPath returns the system-dependent path to the device
+func (hdi *HidDeviceInfo) GetPath() string {
+	return hdi.Path
+}
+
+// IDs returns the vendor and product id of the device
+func (hdi *HidDeviceInfo) IDs() (uint16, uint16, int, uint16) {
+	return hdi.VendorID, hdi.ProductID, hdi.Interface, hdi.UsagePage
+}
+
+// Type returns the type of the device (HID or generic)
+func (hdi *HidDeviceInfo) Type() DeviceType {
+	return DeviceTypeHID
+}

+ 33 - 8
vendor/github.com/karalabe/hid/hid_disabled.go

@@ -4,7 +4,7 @@
 // This file is released under the 3-clause BSD license. Note however that Linux
 // This file is released under the 3-clause BSD license. Note however that Linux
 // support depends on libusb, released under GNU LGPL 2.1 or later.
 // support depends on libusb, released under GNU LGPL 2.1 or later.
 
 
-// +build !linux,!darwin,!windows ios !cgo
+// +build !freebsd,!linux,!darwin,!windows ios !cgo
 
 
 package hid
 package hid
 
 
@@ -22,30 +22,55 @@ func Enumerate(vendorID uint16, productID uint16) []DeviceInfo {
 	return nil
 	return nil
 }
 }
 
 
-// Device is a live HID USB connected device handle. On platforms that this file
+// HidDevice is a live HID USB connected device handle. On platforms that this file
 // implements the type lacks the actual HID device and all methods are noop.
 // implements the type lacks the actual HID device and all methods are noop.
-type Device struct {
-	DeviceInfo // Embed the infos for easier access
+type HidDevice struct {
+	HidDeviceInfo // Embed the infos for easier access
 }
 }
 
 
 // Open connects to an HID device by its path name. On platforms that this file
 // Open connects to an HID device by its path name. On platforms that this file
 // implements the method just returns an error.
 // implements the method just returns an error.
-func (info DeviceInfo) Open() (*Device, error) {
+func (info HidDeviceInfo) Open() (*Device, error) {
 	return nil, ErrUnsupportedPlatform
 	return nil, ErrUnsupportedPlatform
 }
 }
 
 
 // Close releases the HID USB device handle. On platforms that this file implements
 // Close releases the HID USB device handle. On platforms that this file implements
 // the method is just a noop.
 // the method is just a noop.
-func (dev *Device) Close() error { return nil }
+func (dev *HidDevice) Close() error { return ErrUnsupportedPlatform }
 
 
 // Write sends an output report to a HID device. On platforms that this file
 // Write sends an output report to a HID device. On platforms that this file
 // implements the method just returns an error.
 // implements the method just returns an error.
-func (dev *Device) Write(b []byte) (int, error) {
+func (dev *HidDevice) Write(b []byte) (int, error) {
 	return 0, ErrUnsupportedPlatform
 	return 0, ErrUnsupportedPlatform
 }
 }
 
 
 // Read retrieves an input report from a HID device. On platforms that this file
 // Read retrieves an input report from a HID device. On platforms that this file
 // implements the method just returns an error.
 // implements the method just returns an error.
-func (dev *Device) Read(b []byte) (int, error) {
+func (dev *HidDevice) Read(b []byte) (int, error) {
 	return 0, ErrUnsupportedPlatform
 	return 0, ErrUnsupportedPlatform
 }
 }
+
+// Open tries to open the USB device represented by the current DeviceInfo
+func (gdi *GenericDeviceInfo) Open() (Device, error) {
+	return nil, ErrUnsupportedPlatform
+}
+
+// GenericDevice represents a generic USB device
+type GenericDevice struct {
+	*GenericDeviceInfo // Embed the infos for easier access
+}
+
+// Write implements io.ReaderWriter
+func (gd *GenericDevice) Write(b []byte) (int, error) {
+	return 0, ErrUnsupportedPlatform
+}
+
+// Read implements io.ReaderWriter
+func (gd *GenericDevice) Read(b []byte) (int, error) {
+	return 0, ErrUnsupportedPlatform
+}
+
+// Close a previously opened generic USB device
+func (gd *GenericDevice) Close() error {
+	return ErrUnsupportedPlatform
+}

+ 247 - 16
vendor/github.com/karalabe/hid/hid_enabled.go

@@ -4,7 +4,7 @@
 // This file is released under the 3-clause BSD license. Note however that Linux
 // This file is released under the 3-clause BSD license. Note however that Linux
 // support depends on libusb, released under LGNU GPL 2.1 or later.
 // support depends on libusb, released under LGNU GPL 2.1 or later.
 
 
-// +build linux,cgo darwin,!ios,cgo windows,cgo
+// +build freebsd,cgo linux,cgo darwin,!ios,cgo windows,cgo
 
 
 package hid
 package hid
 
 
@@ -13,13 +13,15 @@ package hid
 
 
 #cgo linux CFLAGS: -I./libusb/libusb -DDEFAULT_VISIBILITY="" -DOS_LINUX -D_GNU_SOURCE -DPOLL_NFDS_TYPE=int
 #cgo linux CFLAGS: -I./libusb/libusb -DDEFAULT_VISIBILITY="" -DOS_LINUX -D_GNU_SOURCE -DPOLL_NFDS_TYPE=int
 #cgo linux,!android LDFLAGS: -lrt
 #cgo linux,!android LDFLAGS: -lrt
-#cgo darwin CFLAGS: -DOS_DARWIN
-#cgo darwin LDFLAGS: -framework CoreFoundation -framework IOKit
+#cgo darwin CFLAGS: -DOS_DARWIN -I./libusb/libusb
+#cgo darwin LDFLAGS: -framework CoreFoundation -framework IOKit -lusb-1.0.0
 #cgo windows CFLAGS: -DOS_WINDOWS
 #cgo windows CFLAGS: -DOS_WINDOWS
 #cgo windows LDFLAGS: -lsetupapi
 #cgo windows LDFLAGS: -lsetupapi
+#cgo freebsd CFLAGS: -DOS_FREEBSD
+#cgo freebsd LDFLAGS: -lusb
 
 
 #ifdef OS_LINUX
 #ifdef OS_LINUX
-	#include <sys/poll.h>
+	#include <poll.h>
 	#include "os/threads_posix.c"
 	#include "os/threads_posix.c"
 	#include "os/poll_posix.c"
 	#include "os/poll_posix.c"
 
 
@@ -35,15 +37,50 @@ package hid
 
 
 	#include "hidapi/libusb/hid.c"
 	#include "hidapi/libusb/hid.c"
 #elif OS_DARWIN
 #elif OS_DARWIN
+	#include <libusb.h>
 	#include "hidapi/mac/hid.c"
 	#include "hidapi/mac/hid.c"
 #elif OS_WINDOWS
 #elif OS_WINDOWS
 	#include "hidapi/windows/hid.c"
 	#include "hidapi/windows/hid.c"
+#elif OS_FREEBSD
+    #include <stdlib.h>
+	#include <libusb.h>
+	#include "hidapi/libusb/hid.c"
 #endif
 #endif
+
+#if defined(OS_LINUX) || defined(OS_WINDOWS)
+	void copy_device_list_to_slice(struct libusb_device **data, struct libusb_device **list, int count)
+	{
+		int i;
+		struct libusb_device *current = *list;
+		for (i=0; i<count; i++)
+		{
+			 data[i] = current;
+			 current = list_entry(current->list.next, struct libusb_device, list);
+		}
+	}
+#elif defined(OS_DARWIN) || defined(OS_FREEBSD)
+	void copy_device_list_to_slice(struct libusb_device **data, struct libusb_device **list, int count)
+	{
+		int i;
+		// No memcopy because the struct size isn't available for a sizeof()
+		for (i=0; i<count; i++)
+		{
+			data[i] = list[i];
+		}
+	}
+#endif
+
+const char *usb_strerror(int err)
+{
+	return libusb_strerror(err);
+}
 */
 */
 import "C"
 import "C"
 
 
 import (
 import (
 	"errors"
 	"errors"
+	"fmt"
+	"reflect"
 	"runtime"
 	"runtime"
 	"sync"
 	"sync"
 	"unsafe"
 	"unsafe"
@@ -65,26 +102,132 @@ func Supported() bool {
 	return true
 	return true
 }
 }
 
 
+// genericEnumerate performs generic USB device enumeration
+func genericEnumerate(vendorID uint16, productID uint16) ([]DeviceInfo, error) {
+	var infos []DeviceInfo
+	var ctx *C.struct_libusb_context
+	errCode := int(C.libusb_init((**C.struct_libusb_context)(&ctx)))
+	if errCode < 0 {
+		return nil, fmt.Errorf("Error while initializing libusb: %d", errCode)
+	}
+
+	var deviceListPtr **C.struct_libusb_device
+	count := C.libusb_get_device_list(ctx, (***C.struct_libusb_device)(&deviceListPtr))
+	if count < 0 {
+		return nil, fmt.Errorf("Error code listing devices: %d", count)
+	}
+	defer C.libusb_free_device_list(deviceListPtr, C.int(count))
+
+	deviceList := make([]*C.struct_libusb_device, count)
+	dlhdr := (*reflect.SliceHeader)(unsafe.Pointer(&deviceList))
+	C.copy_device_list_to_slice((**C.struct_libusb_device)(unsafe.Pointer(dlhdr.Data)), deviceListPtr, C.int(count))
+
+	for devnum, dev := range deviceList {
+		var desc C.struct_libusb_device_descriptor
+		errCode := int(C.libusb_get_device_descriptor(dev, &desc))
+		if errCode < 0 {
+			return nil, fmt.Errorf("Error getting device descriptor for generic device %d: %d", devnum, errCode)
+		}
+
+		// Start by checking the vendor id and the product id if necessary
+		if uint16(desc.idVendor) != vendorID || !(productID == 0 || uint16(desc.idProduct) == productID) {
+			continue
+		}
+
+		// Skip HID devices, they will be handled later
+		switch desc.bDeviceClass {
+		case 0:
+			/* Device class is specified at interface level */
+			for cfgnum := 0; cfgnum < int(desc.bNumConfigurations); cfgnum++ {
+				var cfgdesc *C.struct_libusb_config_descriptor
+				errCode = int(C.libusb_get_config_descriptor(dev, C.uint8_t(cfgnum), &cfgdesc))
+				if errCode != 0 {
+					return nil, fmt.Errorf("Error getting device configuration #%d for generic device %d: %d", cfgnum, devnum, errCode)
+				}
+
+				var ifs []C.struct_libusb_interface
+				ifshdr := (*reflect.SliceHeader)(unsafe.Pointer(&ifs))
+				ifshdr.Cap = int(cfgdesc.bNumInterfaces)
+				ifshdr.Len = int(cfgdesc.bNumInterfaces)
+				ifshdr.Data = uintptr(unsafe.Pointer(cfgdesc._interface))
+
+				for ifnum, ifc := range ifs {
+					var ifdescs []C.struct_libusb_interface_descriptor
+					ifdshdr := (*reflect.SliceHeader)(unsafe.Pointer(&ifdescs))
+					ifdshdr.Cap = int(ifc.num_altsetting)
+					ifdshdr.Len = int(ifc.num_altsetting)
+					ifdshdr.Data = uintptr(unsafe.Pointer(ifc.altsetting))
+
+					for _, alt := range ifdescs {
+						if alt.bInterfaceClass != 3 {
+							// Device isn't a HID interface, add them to the device list.
+
+							var endps []C.struct_libusb_endpoint_descriptor
+							endpshdr := (*reflect.SliceHeader)(unsafe.Pointer(&endps))
+							endpshdr.Cap = int(alt.bNumEndpoints)
+							endpshdr.Len = int(alt.bNumEndpoints)
+							endpshdr.Data = uintptr(unsafe.Pointer(alt.endpoint))
+
+							endpoints := make([]GenericEndpoint, alt.bNumEndpoints)
+
+							for ne, endpoint := range endps {
+								endpoints[ne] = GenericEndpoint{
+									Direction:  GenericEndpointDirection(endpoint.bEndpointAddress) & GenericEndpointDirectionIn,
+									Address:    uint8(endpoint.bEndpointAddress),
+									Attributes: uint8(endpoint.bmAttributes),
+								}
+							}
+
+							info := &GenericDeviceInfo{
+								Path:      fmt.Sprintf("%x:%x:%d", vendorID, uint16(desc.idProduct), uint8(C.libusb_get_port_number(dev))),
+								VendorID:  uint16(desc.idVendor),
+								ProductID: uint16(desc.idProduct),
+								device: &GenericDevice{
+									device: dev,
+								},
+								Endpoints: endpoints,
+								Interface: ifnum,
+							}
+							info.device.GenericDeviceInfo = info
+							infos = append(infos, info)
+						}
+					}
+				}
+			}
+		case 3:
+			// Device class is HID, skip it
+			continue
+		}
+	}
+
+	return infos, nil
+}
+
 // Enumerate returns a list of all the HID devices attached to the system which
 // Enumerate returns a list of all the HID devices attached to the system which
 // match the vendor and product id:
 // match the vendor and product id:
 //  - If the vendor id is set to 0 then any vendor matches.
 //  - If the vendor id is set to 0 then any vendor matches.
 //  - If the product id is set to 0 then any product matches.
 //  - If the product id is set to 0 then any product matches.
 //  - If the vendor and product id are both 0, all HID devices are returned.
 //  - If the vendor and product id are both 0, all HID devices are returned.
-func Enumerate(vendorID uint16, productID uint16) []DeviceInfo {
+func Enumerate(vendorID uint16, productID uint16) ([]DeviceInfo, error) {
 	enumerateLock.Lock()
 	enumerateLock.Lock()
 	defer enumerateLock.Unlock()
 	defer enumerateLock.Unlock()
 
 
+	infos, err := genericEnumerate(vendorID, productID)
+
+	if err != nil {
+		return nil, err
+	}
+
 	// Gather all device infos and ensure they are freed before returning
 	// Gather all device infos and ensure they are freed before returning
 	head := C.hid_enumerate(C.ushort(vendorID), C.ushort(productID))
 	head := C.hid_enumerate(C.ushort(vendorID), C.ushort(productID))
 	if head == nil {
 	if head == nil {
-		return nil
+		return nil, nil
 	}
 	}
 	defer C.hid_free_enumeration(head)
 	defer C.hid_free_enumeration(head)
 
 
 	// Iterate the list and retrieve the device details
 	// Iterate the list and retrieve the device details
-	var infos []DeviceInfo
 	for ; head != nil; head = head.next {
 	for ; head != nil; head = head.next {
-		info := DeviceInfo{
+		info := &HidDeviceInfo{
 			Path:      C.GoString(head.path),
 			Path:      C.GoString(head.path),
 			VendorID:  uint16(head.vendor_id),
 			VendorID:  uint16(head.vendor_id),
 			ProductID: uint16(head.product_id),
 			ProductID: uint16(head.product_id),
@@ -104,11 +247,11 @@ func Enumerate(vendorID uint16, productID uint16) []DeviceInfo {
 		}
 		}
 		infos = append(infos, info)
 		infos = append(infos, info)
 	}
 	}
-	return infos
+	return infos, nil
 }
 }
 
 
 // Open connects to an HID device by its path name.
 // Open connects to an HID device by its path name.
-func (info DeviceInfo) Open() (*Device, error) {
+func (info *HidDeviceInfo) Open() (Device, error) {
 	enumerateLock.Lock()
 	enumerateLock.Lock()
 	defer enumerateLock.Unlock()
 	defer enumerateLock.Unlock()
 
 
@@ -119,14 +262,14 @@ func (info DeviceInfo) Open() (*Device, error) {
 	if device == nil {
 	if device == nil {
 		return nil, errors.New("hidapi: failed to open device")
 		return nil, errors.New("hidapi: failed to open device")
 	}
 	}
-	return &Device{
+	return &HidDevice{
 		DeviceInfo: info,
 		DeviceInfo: info,
 		device:     device,
 		device:     device,
 	}, nil
 	}, nil
 }
 }
 
 
-// Device is a live HID USB connected device handle.
-type Device struct {
+// HidDevice is a live HID USB connected device handle.
+type HidDevice struct {
 	DeviceInfo // Embed the infos for easier access
 	DeviceInfo // Embed the infos for easier access
 
 
 	device *C.hid_device // Low level HID device to communicate through
 	device *C.hid_device // Low level HID device to communicate through
@@ -134,7 +277,7 @@ type Device struct {
 }
 }
 
 
 // Close releases the HID USB device handle.
 // Close releases the HID USB device handle.
-func (dev *Device) Close() error {
+func (dev *HidDevice) Close() error {
 	dev.lock.Lock()
 	dev.lock.Lock()
 	defer dev.lock.Unlock()
 	defer dev.lock.Unlock()
 
 
@@ -149,7 +292,7 @@ func (dev *Device) Close() error {
 //
 //
 // Write will send the data on the first OUT endpoint, if one exists. If it does
 // Write will send the data on the first OUT endpoint, if one exists. If it does
 // not, it will send the data through the Control Endpoint (Endpoint 0).
 // not, it will send the data through the Control Endpoint (Endpoint 0).
-func (dev *Device) Write(b []byte) (int, error) {
+func (dev *HidDevice) Write(b []byte) (int, error) {
 	// Abort if nothing to write
 	// Abort if nothing to write
 	if len(b) == 0 {
 	if len(b) == 0 {
 		return 0, nil
 		return 0, nil
@@ -192,7 +335,7 @@ func (dev *Device) Write(b []byte) (int, error) {
 }
 }
 
 
 // Read retrieves an input report from a HID device.
 // Read retrieves an input report from a HID device.
-func (dev *Device) Read(b []byte) (int, error) {
+func (dev *HidDevice) Read(b []byte) (int, error) {
 	// Aborth if nothing to read
 	// Aborth if nothing to read
 	if len(b) == 0 {
 	if len(b) == 0 {
 		return 0, nil
 		return 0, nil
@@ -226,3 +369,91 @@ func (dev *Device) Read(b []byte) (int, error) {
 	}
 	}
 	return read, nil
 	return read, nil
 }
 }
+
+// Type identify the device as a HID device
+func (dev *HidDevice) Type() DeviceType {
+	return dev.DeviceInfo.Type()
+}
+
+// Open tries to open the USB device represented by the current DeviceInfo
+func (gdi *GenericDeviceInfo) Open() (Device, error) {
+	var handle *C.struct_libusb_device_handle
+	errCode := int(C.libusb_open(gdi.device.device, (**C.struct_libusb_device_handle)(&handle)))
+	if errCode < 0 {
+		return nil, fmt.Errorf("Error opening generic USB device %v, code %d", gdi.device.handle, errCode)
+	}
+
+	gdi.device.handle = handle
+	// QUESTION: ai-je deja initialie le GDI ?
+	// 	GenericDeviceInfo: gdi,
+	// 	handle:            handle,
+	// }
+
+	for _, endpoint := range gdi.Endpoints {
+		switch {
+		case endpoint.Direction == GenericEndpointDirectionOut && endpoint.Attributes == GenericEndpointAttributeInterrupt:
+			gdi.device.WEndpoint = endpoint.Address
+		case endpoint.Direction == GenericEndpointDirectionIn && endpoint.Attributes == GenericEndpointAttributeInterrupt:
+			gdi.device.REndpoint = endpoint.Address
+		}
+	}
+
+	if gdi.device.REndpoint == 0 || gdi.device.WEndpoint == 0 {
+		return nil, fmt.Errorf("Missing endpoint in device %#x:%#x:%d", gdi.VendorID, gdi.ProductID, gdi.Interface)
+	}
+
+	return gdi.device, nil
+}
+
+// GenericDevice represents a generic USB device
+type GenericDevice struct {
+	*GenericDeviceInfo // Embed the infos for easier access
+
+	REndpoint uint8
+	WEndpoint uint8
+
+	device *C.struct_libusb_device
+	handle *C.struct_libusb_device_handle
+	lock   sync.Mutex
+}
+
+// Write implements io.ReaderWriter
+func (gd *GenericDevice) Write(b []byte) (int, error) {
+	gd.lock.Lock()
+	defer gd.lock.Unlock()
+
+	out, err := interruptTransfer(gd.handle, gd.WEndpoint, b)
+	return len(out), err
+}
+
+// Read implements io.ReaderWriter
+func (gd *GenericDevice) Read(b []byte) (int, error) {
+	gd.lock.Lock()
+	defer gd.lock.Unlock()
+
+	out, err := interruptTransfer(gd.handle, gd.REndpoint, b)
+	return len(out), err
+}
+
+// Close a previously opened generic USB device
+func (gd *GenericDevice) Close() error {
+	gd.lock.Lock()
+	defer gd.lock.Unlock()
+
+	if gd.handle != nil {
+		C.libusb_close(gd.handle)
+		gd.handle = nil
+	}
+
+	return nil
+}
+
+// interruptTransfer is a helpler function for libusb's interrupt transfer function
+func interruptTransfer(handle *C.struct_libusb_device_handle, endpoint uint8, data []byte) ([]byte, error) {
+	var transferred C.int
+	errCode := int(C.libusb_interrupt_transfer(handle, (C.uchar)(endpoint), (*C.uchar)(&data[0]), (C.int)(len(data)), &transferred, (C.uint)(0)))
+	if errCode != 0 {
+		return nil, fmt.Errorf("Interrupt transfer error: %s", C.GoString(C.usb_strerror(C.int(errCode))))
+	}
+	return data[:int(transferred)], nil
+}

+ 53 - 0
vendor/github.com/karalabe/hid/usb.go

@@ -0,0 +1,53 @@
+// hid - Gopher Interface Devices (USB HID)
+// Copyright (c) 2019 Péter Szilágyi, Guillaume Ballet. All rights reserved.
+//
+// This file is released under the 3-clause BSD license. Note however that Linux
+// support depends on libusb, released under GNU LGPL 2.1 or later.
+
+// Package usb provide interfaces for generic USB devices.
+package hid
+
+// DeviceType represents the type of a USB device (generic or HID)
+type DeviceType int
+
+// List of supported device types
+const (
+	DeviceTypeGeneric DeviceType = 0
+	DeviceTypeHID     DeviceType = 1
+)
+
+// Enumerate returns a list of all the HID devices attached to the system which
+// match the vendor and product id:
+//  - If the vendor id is set to 0 then any vendor matches.
+//  - If the product id is set to 0 then any product matches.
+//  - If the vendor and product id are both 0, all HID devices are returned.
+// func Enumerate(vendorID uint16, productID uint16) []DeviceInfo {
+// }
+
+// DeviceInfo is a generic libusb info interface
+type DeviceInfo interface {
+	// Type returns the type of the device (generic or HID)
+	Type() DeviceType
+
+	// Platform-specific device path
+	GetPath() string
+
+	// IDs returns the vendor and product IDs for the device,
+	// as well as the endpoint id and the usage page.
+	IDs() (uint16, uint16, int, uint16)
+
+	// Open tries to open the USB device represented by the current DeviceInfo
+	Open() (Device, error)
+}
+
+// Device is a generic libusb device interface
+type Device interface {
+	Close() error
+
+	Write(b []byte) (int, error)
+
+	Read(b []byte) (int, error)
+
+	// Type returns the type of the device (generic or HID)
+	Type() DeviceType
+}

+ 1 - 1
vendor/github.com/karalabe/hid/wchar.go

@@ -7,7 +7,7 @@
 // https://github.com/orofarne/gowchar/blob/master/LICENSE
 // https://github.com/orofarne/gowchar/blob/master/LICENSE
 
 
 // +build !ios
 // +build !ios
-// +build linux darwin windows
+// +build freebsd linux darwin windows
 
 
 package hid
 package hid
 
 

+ 3 - 3
vendor/vendor.json

@@ -267,10 +267,10 @@
 			"revisionTime": "2017-04-30T22:20:11Z"
 			"revisionTime": "2017-04-30T22:20:11Z"
 		},
 		},
 		{
 		{
-			"checksumSHA1": "6XsjAARQFvlW6dS15al0ibTFPOQ=",
+			"checksumSHA1": "p6UjFsx/1ACWAhsdEOWrXAHptGY=",
 			"path": "github.com/karalabe/hid",
 			"path": "github.com/karalabe/hid",
-			"revision": "d815e0c1a2e2082a287a2806bc90bc8fc7b276a9",
-			"revisionTime": "2018-11-28T19:21:57Z",
+			"revision": "e40407cce1c217644c09da5415bbfb07d330ea5e",
+			"revisionTime": "2019-05-28T15:16:06Z",
 			"tree": true
 			"tree": true
 		},
 		},
 		{
 		{