├── .gitignore ├── .travis.yml ├── AUTHORS.md ├── Justfile ├── LICENSE.txt ├── README.md ├── Vagrantfile ├── asyncio.go ├── configuration.go ├── context.go ├── context_test.go ├── descriptors.go ├── descriptors_test.go ├── device.go ├── doc.go ├── endpoint.go ├── examples ├── control_transfer │ ├── go.mod │ └── main.go ├── find_printer │ ├── go.mod │ └── main.go ├── get_sn │ ├── get_sn │ ├── go.mod │ └── main.go ├── get_sn_lite │ ├── get_sn_lite │ ├── go.mod │ └── main.go ├── hotplug │ ├── go.mod │ └── main.go ├── key33220 │ ├── go.mod │ ├── go.sum │ ├── key33220 │ └── main.go └── keyu2751a │ ├── go.mod │ ├── go.sum │ ├── keyu2751a │ └── main.go ├── go.mod ├── handle.go ├── hotplug.go ├── interfaces.go ├── miscellaneous.go ├── miscellaneous_test.go ├── setupdata.go ├── speeds.go ├── syncio.go ├── version.go └── version_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | .vagrant/ 10 | 11 | # Architecture specific extensions/prefixes 12 | *.[568vq] 13 | [568vq].out 14 | 15 | *.cgo1.go 16 | *.cgo2.c 17 | _cgo_defun.c 18 | _cgo_gotypes.go 19 | _cgo_export.* 20 | 21 | _testmain.go 22 | 23 | *.exe 24 | *.test 25 | *.prof 26 | 27 | 28 | # Ignore the covrage analyses 29 | coverage.out 30 | 31 | # Ignore executables 32 | examples/control_transfer/control_transfer 33 | examples/find_printer/find_printer 34 | examples/get_sn/get_sn 35 | examples/get_sn_lite/get_sn_lite 36 | examples/hotplug/hotplug 37 | examples/key33220/key33220 38 | examples/keyu2751a/keyu2751a 39 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | os: 4 | - osx 5 | 6 | go: 7 | - "1.12.x" 8 | - "1.13.x" 9 | - "1.14.x" 10 | - "1.15.x" 11 | 12 | script: 13 | - go test -v ./... 14 | 15 | before_install: 16 | - brew upgrade libusb 17 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | # AUTHORS.md 2 | 3 | This file lists the authors, maintainers, and contributors for the 4 | [libusb][] project. Thank you to everyone for their help. 5 | 6 | ## Authors 7 | - [Matthew Rankin][mdr] 8 | 9 | ## Maintainers 10 | - None at this time. 11 | 12 | ## Contributors 13 | - [rojer][] 14 | - [jpoirier][] 15 | 16 | [libusb]: https://github.com/gotmc/libusb 17 | [mdr]: https://github.com/matthewrankin 18 | [jpoirier]: https://github.com/jpoirier 19 | [rojer]: https://github.com/rojer 20 | -------------------------------------------------------------------------------- /Justfile: -------------------------------------------------------------------------------- 1 | # -*- Justfile -*- 2 | 3 | app_name := "libusb" 4 | coverage_file := "coverage.out" 5 | 6 | # List the available justfile recipes. 7 | [group('general')] 8 | @default: 9 | just --list --unsorted 10 | 11 | # List the lines of code in the project. 12 | [group('general')] 13 | loc: 14 | scc --remap-unknown "-*- Justfile -*-":"justfile" 15 | 16 | # List the outdated direct dependencies (can be slow). 17 | [group('dependencies')] 18 | outdated: 19 | # (requires https://github.com/psampaz/go-mod-outdated). 20 | go list -u -m -json all | go-mod-outdated -update -direct 21 | 22 | # Run go mod tidy and verify. 23 | [group('dependencies')] 24 | tidy: 25 | go mod tidy 26 | go mod verify 27 | 28 | # Format and vet Go code. Runs before tests. 29 | [group('test')] 30 | check: 31 | go fmt ./... 32 | go vet ./... 33 | 34 | # Lint using staticcheck. Format and vet code. 35 | [group('test')] 36 | lint: check 37 | staticcheck -f stylish ./... 38 | 39 | # Run the unit tests. 40 | [group('test')] 41 | unit *FLAGS: check 42 | go test ./... -cover -vet=off -race {{FLAGS}} -short 43 | 44 | # Run the integration tests. 45 | [group('test')] 46 | int *FLAGS: check 47 | go test ./... -cover -vet=off -race {{FLAGS}} -run Integration 48 | 49 | # HTML report for unit (default), int, e2e, or all tests. 50 | [group('test')] 51 | cover test='unit': check 52 | go test ./... -vet=off -coverprofile={{coverage_file}} \ 53 | {{ if test == 'all' { '' } \ 54 | else if test == 'int' { '-run Integration' } \ 55 | else if test == 'e2e' { '-run E2E' } \ 56 | else { '-short' } }} 57 | go tool cover -html={{coverage_file}} 58 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2025 The libusb developers (see AUTHORS.md file) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libusb 2 | 3 | Go bindings for the [libusb C library][libusb-c]. 4 | 5 | [![GoDoc][godoc badge]][godoc link] 6 | [![Go Report Card][report badge]][report card] 7 | [![Build Status][travis image]][travis link] 8 | [![License Badge][license image]][LICENSE.txt] 9 | 10 | # Installation 11 | 12 | ```bash 13 | $ go get github.com/gotmc/libusb/v2 14 | ``` 15 | 16 | ## Installing C libusb library 17 | 18 | To use [libusb][] package, you'll need to install the [libusb C 19 | library][libusb-c] first. 20 | 21 | ### OS X 22 | 23 | ```bash 24 | $ brew install libusb 25 | ``` 26 | 27 | ### Windows 28 | 29 | Download and install the latest Windows libusb binaries from 30 | [libusb.info][libusb-c]. 31 | 32 | ### Linux 33 | 34 | ```bash 35 | $ sudo apt-get install -y libusb-dev libusb-1.0-0-dev 36 | ``` 37 | 38 | # Documentation 39 | 40 | Documentation can be found at either: 41 | 42 | - 43 | - after running `$ 44 | godoc -http=:6060` 45 | 46 | ## Contributing 47 | 48 | Contributions are welcome! To contribute please: 49 | 50 | 1. Fork the repository 51 | 2. Create a feature branch 52 | 3. Code 53 | 4. Submit a [pull request][] 54 | 55 | ### Testing 56 | 57 | Prior to submitting a [pull request][], please run: 58 | 59 | ```bash 60 | $ make check 61 | $ make lint 62 | ``` 63 | 64 | To update and view the test coverage report: 65 | 66 | ```bash 67 | $ make cover 68 | ``` 69 | 70 | ## Alternatives 71 | 72 | There are other USB Go libraries besides [libusb][]. Below are a few 73 | alternatives: 74 | 75 | - [google/gousb][] — Wraps the [libusb C library][libusb-c] to provde 76 | Go-bindings. This library supersedes [kylelemons/gousb][], which is not 77 | archived. Apachage-2.0 license. 78 | - [karalabe/usb][] — Does not require the [libusb C library][libusb-c] to be 79 | installed. Written in C to be a cross platform, fully self-contained library 80 | for accessing and communicating with USB devices either via HID or low level 81 | interrupts. LGPL-3.0 license. 82 | - [deadsy/libusb][] — Wraps the [libusb C library][libusb-c]. MIT license. As of 83 | 05-Apr-24, this package hasn't been updated in six years. 84 | 85 | ## License 86 | 87 | [libusb][] is released under the MIT license. Please see the 88 | [LICENSE.txt][] file for more information. 89 | 90 | [deadsy/libusb]: https://github.com/deadsy/libusb 91 | [godoc badge]: https://godoc.org/github.com/gotmc/libusb?status.svg 92 | [godoc link]: https://godoc.org/github.com/gotmc/libusb 93 | [google/gousb]: https://github.com/google/gousb 94 | [karalabe/usb]: https://github.com/karalabe/usb 95 | [kylelemons/gousb]: https://github.com/kylelemons/gousb 96 | [libusb]: https://github.com/gotmc/libusb 97 | [libusb-c]: http://libusb.info 98 | [LICENSE.txt]: https://github.com/gotmc/libusb/blob/master/LICENSE.txt 99 | [license image]: https://img.shields.io/badge/license-MIT-blue.svg 100 | [pull request]: https://help.github.com/articles/using-pull-requests 101 | [report badge]: https://goreportcard.com/badge/github.com/gotmc/libusb 102 | [report card]: https://goreportcard.com/report/github.com/gotmc/libusb 103 | [travis image]: http://img.shields.io/travis/gotmc/libusb/master.svg 104 | [travis link]: https://travis-ci.org/gotmc/libusb 105 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | VAGRANTFILE_API_VER = "2" 5 | Vagrant.configure(VAGRANTFILE_API_VER) do |config| 6 | config.vm.box = "ubuntu/trusty64" 7 | 8 | # Add Linux related disk images to shared folder 9 | # config.vm.synced_folder "~/Disk Images/Linux", "/vagrant_disk_images" 10 | 11 | # Enable provisioning with a shell script. 12 | config.vm.provision "shell", inline: <<-SHELL 13 | apt-get update 14 | apt-get install -y git curl vim tree 15 | apt-get install -y pkg-config libusb-1.0-0 libusb-1.0-0-dev 16 | echo "### Setting up dotfiles" 17 | su vagrant -c 'mkdir -p development/go/src' 18 | su vagrant -l -c 'cd ~/development && git clone https://github.com/matthewrankin/dotfiles.git' 19 | su vagrant -l -c 'cd ~/development/dotfiles && ./deploy-dot-files.py' 20 | echo '### Installing Go 1.8' 21 | cd /tmp && wget -q https://storage.googleapis.com/golang/go1.8.1.linux-amd64.tar.gz 22 | tar -C /usr/local -xzf /tmp/go1.8.1.linux-amd64.tar.gz 23 | rm /tmp/go1.8.1.linux-amd64.tar.gz 24 | su vagrant -l -c '/usr/local/go/bin/go get -u github.com/golang/lint/golint' 25 | su vagrant -l -c '/usr/local/go/bin/go get github.com/gotmc/libusb' 26 | echo '### Show version of libusb installed' 27 | su vagrant -l -c 'pkg-config --modversion libusb-1.0' 28 | SHELL 29 | end 30 | -------------------------------------------------------------------------------- /asyncio.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2025 The libusb developers. All rights reserved. 2 | // Project site: https://github.com/gotmc/libusb 3 | // Use of this source code is governed by a MIT-style license that 4 | // can be found in the LICENSE.txt file for the project. 5 | 6 | package libusb 7 | 8 | // #cgo pkg-config: libusb-1.0 9 | // #include 10 | import "C" 11 | -------------------------------------------------------------------------------- /configuration.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2025 The libusb developers. All rights reserved. 2 | // Project site: https://github.com/gotmc/libusb 3 | // Use of this source code is governed by a MIT-style license that 4 | // can be found in the LICENSE.txt file for the project. 5 | 6 | package libusb 7 | 8 | // Config models the USB configuration. 9 | type Config struct { 10 | *ConfigDescriptor 11 | Device *Device 12 | } 13 | 14 | // ConfigDescriptor models the descriptor for the USB configuration 15 | type ConfigDescriptor struct { 16 | Length int 17 | DescriptorType descriptorType 18 | TotalLength uint16 19 | NumInterfaces int 20 | ConfigurationValue uint8 21 | ConfigurationIndex uint8 22 | Attributes uint8 23 | MaxPowerMilliAmperes uint 24 | SupportedInterfaces 25 | } 26 | -------------------------------------------------------------------------------- /context.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2025 The libusb developers. All rights reserved. 2 | // Project site: https://github.com/gotmc/libusb 3 | // Use of this source code is governed by a MIT-style license that 4 | // can be found in the LICENSE.txt file for the project. 5 | 6 | package libusb 7 | 8 | // #cgo pkg-config: libusb-1.0 9 | // #include 10 | // /* Define LIBUSB_OPTION_LOG_LEVEL if not available in the libusb version */ 11 | // #if !defined(LIBUSB_OPTION_LOG_LEVEL) && defined(LIBUSB_API_VERSION) && (LIBUSB_API_VERSION >= 0x01000107) 12 | // #define LIBUSB_OPTION_LOG_LEVEL 1 13 | // #endif 14 | // 15 | // int set_debug(libusb_context * ctx, int level) { 16 | // #if defined(HAVE_LIBUSB_SET_OPTION) && defined(LIBUSB_OPTION_LOG_LEVEL) 17 | // return libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, level); 18 | // #else 19 | // libusb_set_debug(ctx, level); 20 | // return 0; 21 | // #endif 22 | // } 23 | import "C" 24 | 25 | import ( 26 | "fmt" 27 | "unsafe" 28 | ) 29 | 30 | // LogLevel is an enum for the C libusb log message levels. 31 | type LogLevel int 32 | 33 | // Log message levels 34 | // 35 | // http://bit.ly/enum_libusb_log_level 36 | const ( 37 | LogLevelNone LogLevel = C.LIBUSB_LOG_LEVEL_NONE 38 | LogLevelError LogLevel = C.LIBUSB_LOG_LEVEL_ERROR 39 | LogLevelWarning LogLevel = C.LIBUSB_LOG_LEVEL_WARNING 40 | LogLevelInfo LogLevel = C.LIBUSB_LOG_LEVEL_INFO 41 | LogLevelDebug LogLevel = C.LIBUSB_LOG_LEVEL_DEBUG 42 | ) 43 | 44 | var logLevels = map[LogLevel]string{ 45 | LogLevelNone: "No messages ever printed by the library (default)", 46 | LogLevelError: "Error messages are printed to stderr", 47 | LogLevelWarning: "Warning and error messages are printed to stderr", 48 | LogLevelInfo: "Informational messages are printed to stdout, warning and error messages are printed to stderr", 49 | LogLevelDebug: "Debug and informational messages are printed to stdout, warnings and errors to stderr", 50 | } 51 | 52 | func (level LogLevel) String() string { 53 | return logLevels[level] 54 | } 55 | 56 | // Context represents a libusb session/context. 57 | type Context struct { 58 | libusbContext *C.libusb_context 59 | LogLevel LogLevel 60 | } 61 | 62 | // NewContext intializes a new libusb session/context by creating a new 63 | // Context and returning a pointer to that Context. 64 | func NewContext() (*Context, error) { 65 | newContext := &Context{ 66 | LogLevel: LogLevelNone, 67 | } 68 | errnum := C.libusb_init(&newContext.libusbContext) 69 | if errnum != 0 { 70 | return nil, fmt.Errorf( 71 | "failed to initialize new libusb context; received error %d", errnum) 72 | } 73 | return newContext, nil 74 | } 75 | 76 | // Close deinitializes the libusb session/context. 77 | func (ctx *Context) Close() error { 78 | C.libusb_exit(ctx.libusbContext) 79 | ctx.libusbContext = nil 80 | return nil 81 | } 82 | 83 | // SetDebug sets the log message verbosity. 84 | func (ctx *Context) SetDebug(level LogLevel) { 85 | C.set_debug(ctx.libusbContext, C.int(level)) 86 | ctx.LogLevel = level 87 | } 88 | 89 | // DeviceList returns an array of devices for the context. 90 | func (ctx *Context) DeviceList() ([]*Device, error) { 91 | var devices []*Device 92 | var list **C.libusb_device 93 | const unrefDevices = 1 94 | numDevicesFound := int(C.libusb_get_device_list(ctx.libusbContext, &list)) 95 | if numDevicesFound < 0 { 96 | return nil, ErrorCode(numDevicesFound) 97 | } 98 | defer C.libusb_free_device_list(list, unrefDevices) 99 | libusbDevices := unsafe.Slice(list, numDevicesFound) 100 | // var libusbDevices []*C.libusb_device 101 | // *(*reflect.SliceHeader)(unsafe.Pointer(&libusbDevices)) = reflect.SliceHeader{ 102 | // Data: uintptr(unsafe.Pointer(list)), 103 | // Len: numDevicesFound, 104 | // Cap: numDevicesFound, 105 | // } 106 | 107 | for _, thisLibusbDevice := range libusbDevices { 108 | thisDevice := Device{ 109 | libusbDevice: thisLibusbDevice, 110 | } 111 | devices = append(devices, &thisDevice) 112 | } 113 | return devices, nil 114 | } 115 | 116 | // OpenDeviceWithVendorProduct opens a USB device using the VendorID and 117 | // productID and then returns a device handle. 118 | func (ctx *Context) OpenDeviceWithVendorProduct( 119 | vendorID uint16, 120 | productID uint16, 121 | ) (*Device, *DeviceHandle, error) { 122 | var deviceHandle DeviceHandle 123 | deviceHandle.libusbDeviceHandle = C.libusb_open_device_with_vid_pid( 124 | ctx.libusbContext, C.uint16_t(vendorID), C.uint16_t(productID)) 125 | if deviceHandle.libusbDeviceHandle == nil { 126 | return nil, nil, fmt.Errorf("could not open USB device %v:%v", 127 | vendorID, 128 | productID, 129 | ) 130 | } 131 | device := Device{ 132 | libusbDevice: C.libusb_get_device(deviceHandle.libusbDeviceHandle), 133 | } 134 | return &device, &deviceHandle, nil 135 | } 136 | -------------------------------------------------------------------------------- /context_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2025 The libusb developers. All rights reserved. 2 | // Project site: https://github.com/gotmc/libusb 3 | // Use of this source code is governed by a MIT-style license that 4 | // can be found in the LICENSE.txt file for the project. 5 | 6 | package libusb 7 | 8 | import "testing" 9 | 10 | func TestNewContext(t *testing.T) { 11 | if _, err := NewContext(); err != nil { 12 | t.Errorf( 13 | "Error initializing new libusb context:\n\tgot %v want %v", 14 | err, 15 | nil, 16 | ) 17 | } 18 | } 19 | 20 | func TestCloseContext(t *testing.T) { 21 | context, _ := NewContext() 22 | if err := context.Close(); err != nil { 23 | t.Errorf( 24 | "Error exiting context:\n\tgot %v want %v", 25 | err, 26 | nil, 27 | ) 28 | } 29 | if context.libusbContext != nil { 30 | t.Errorf( 31 | "Context field still exists after exiting:\n\tgot %v want %v", 32 | context.libusbContext, 33 | nil, 34 | ) 35 | } 36 | } 37 | 38 | func TestSetDebugLevel(t *testing.T) { 39 | testCases := []struct { 40 | lev LogLevel 41 | }{ 42 | {LogLevelNone}, 43 | {LogLevelError}, 44 | {LogLevelWarning}, 45 | {LogLevelInfo}, 46 | {LogLevelDebug}, 47 | } 48 | for _, tc := range testCases { 49 | context, _ := NewContext() 50 | context.SetDebug(tc.lev) 51 | if got := context.LogLevel; got != tc.lev { 52 | t.Errorf("got %v; want %v", got, tc.lev) 53 | } 54 | } 55 | } 56 | 57 | func TestLogLevelStringMethod(t *testing.T) { 58 | testCases := []struct { 59 | logLevel LogLevel 60 | want string 61 | }{ 62 | {LogLevelNone, "No messages ever printed by the library (default)"}, 63 | {LogLevelError, "Error messages are printed to stderr"}, 64 | {LogLevelWarning, "Warning and error messages are printed to stderr"}, 65 | { 66 | LogLevelInfo, 67 | "Informational messages are printed to stdout, warning and error messages are printed to stderr", 68 | }, 69 | { 70 | LogLevelDebug, 71 | "Debug and informational messages are printed to stdout, warnings and errors to stderr", 72 | }, 73 | } 74 | for _, tc := range testCases { 75 | if got := tc.logLevel.String(); got != tc.want { 76 | t.Errorf("got %s; want %s", got, tc.want) 77 | } 78 | } 79 | } 80 | 81 | func TestDeviceList(t *testing.T) { 82 | context, _ := NewContext() 83 | defer context.Close() 84 | devices, err := context.DeviceList() 85 | if err != nil { 86 | t.Errorf( 87 | "Error on DeviceList:\n\tgot %v; want %v", 88 | err, 89 | nil, 90 | ) 91 | } 92 | if got := len(devices); got < 1 { 93 | t.Errorf( 94 | "got %v device; want at least one", 95 | got, 96 | ) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /descriptors.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2025 The libusb developers. All rights reserved. 2 | // Project site: https://github.com/gotmc/libusb 3 | // Use of this source code is governed by a MIT-style license that 4 | // can be found in the LICENSE.txt file for the project. 5 | 6 | package libusb 7 | 8 | // #cgo pkg-config: libusb-1.0 9 | // #include 10 | import "C" 11 | import "fmt" 12 | 13 | type classCode byte 14 | type bcd uint16 15 | 16 | // String implements the Stringer interface for bcd. 17 | func (b bcd) String() string { 18 | return fmt.Sprintf("%#04x (%2.2f)", 19 | uint16(b), 20 | b.AsDecimal(), 21 | ) 22 | } 23 | 24 | // AsDecimal converts the BCD value with a format 0xJJMN into a decimal JJ.MN 25 | // where JJ is the major version number, M is the minor version, and N is the 26 | // sub-minor version number. 27 | func (b bcd) AsDecimal() float64 { 28 | return bcdToDecimal(uint16(b)) 29 | } 30 | 31 | const ( 32 | perInterface classCode = C.LIBUSB_CLASS_PER_INTERFACE 33 | audio classCode = C.LIBUSB_CLASS_AUDIO 34 | comm classCode = C.LIBUSB_CLASS_COMM 35 | hid classCode = C.LIBUSB_CLASS_HID 36 | physical classCode = C.LIBUSB_CLASS_PHYSICAL 37 | printer classCode = C.LIBUSB_CLASS_PRINTER 38 | ptp classCode = C.LIBUSB_CLASS_PTP 39 | image classCode = C.LIBUSB_CLASS_IMAGE 40 | massStorage classCode = C.LIBUSB_CLASS_MASS_STORAGE 41 | hub classCode = C.LIBUSB_CLASS_HUB 42 | data classCode = C.LIBUSB_CLASS_DATA 43 | smartCard classCode = C.LIBUSB_CLASS_SMART_CARD 44 | contentSecurity classCode = C.LIBUSB_CLASS_CONTENT_SECURITY 45 | video classCode = C.LIBUSB_CLASS_VIDEO 46 | personalHealthcare classCode = C.LIBUSB_CLASS_PERSONAL_HEALTHCARE 47 | diagnosticDevice classCode = C.LIBUSB_CLASS_DIAGNOSTIC_DEVICE 48 | wireless classCode = C.LIBUSB_CLASS_WIRELESS 49 | application classCode = C.LIBUSB_CLASS_APPLICATION 50 | vendorSpec classCode = C.LIBUSB_CLASS_VENDOR_SPEC 51 | ) 52 | 53 | var classCodes = map[classCode]string{ 54 | perInterface: "Each interface specifies its own class information and all interfaces operate independently.", 55 | audio: "Audio class.", 56 | comm: "Communications class.", 57 | hid: "Human Interface Device class.", 58 | physical: "Physical.", 59 | printer: "Printer class.", 60 | image: "Image class.", 61 | massStorage: "Mass storage class.", 62 | hub: "Hub class.", 63 | data: "Data class.", 64 | smartCard: "Smart Card.", 65 | contentSecurity: "Content Security.", 66 | video: "Video.", 67 | personalHealthcare: "Personal Healthcare.", 68 | diagnosticDevice: "Diagnostic Device.", 69 | wireless: "Wireless class.", 70 | application: "Application class.", 71 | vendorSpec: "Class is vendor-specific.", 72 | } 73 | 74 | // String implements the Stringer interface for classCode. 75 | func (classCode classCode) String() string { 76 | return classCodes[classCode] 77 | } 78 | 79 | type descriptorType byte 80 | 81 | const ( 82 | descDevice descriptorType = C.LIBUSB_DT_DEVICE 83 | descConfig descriptorType = C.LIBUSB_DT_CONFIG 84 | descString descriptorType = C.LIBUSB_DT_STRING 85 | descInterface descriptorType = C.LIBUSB_DT_INTERFACE 86 | descEndpoint descriptorType = C.LIBUSB_DT_ENDPOINT 87 | descBos descriptorType = C.LIBUSB_DT_BOS 88 | descDeviceCapability descriptorType = C.LIBUSB_DT_DEVICE_CAPABILITY 89 | descHid descriptorType = C.LIBUSB_DT_HID 90 | descReport descriptorType = C.LIBUSB_DT_REPORT 91 | descPhysical descriptorType = C.LIBUSB_DT_PHYSICAL 92 | descHub descriptorType = C.LIBUSB_DT_HUB 93 | descSuperspeedHub descriptorType = C.LIBUSB_DT_SUPERSPEED_HUB 94 | descEndpointCompanion descriptorType = C.LIBUSB_DT_SS_ENDPOINT_COMPANION 95 | ) 96 | 97 | var descriptorTypes = map[descriptorType]string{ 98 | descDevice: "Device descriptor.", 99 | descConfig: "Configuration descriptor.", 100 | descString: "String descriptor.", 101 | descInterface: "Interface descriptor.", 102 | descEndpoint: "Endpoint descriptor.", 103 | descBos: "BOS descriptor.", 104 | descDeviceCapability: "Device Capability descriptor.", 105 | descHid: "HID descriptor.", 106 | descReport: "HID report descriptor.", 107 | descPhysical: "Physical descriptor.", 108 | descHub: "Hub descriptor.", 109 | descSuperspeedHub: "SuperSpeed Hub descriptor.", 110 | descEndpointCompanion: "SuperSpeed Endpoint Companion descriptor.", 111 | } 112 | 113 | func (descriptorType descriptorType) String() string { 114 | return descriptorTypes[descriptorType] 115 | } 116 | 117 | // EndpointDirection provides the type for an in or out endpoint. 118 | type EndpointDirection byte 119 | 120 | const ( 121 | // Per USB 2.0 spec bit 7 of the endpoint address defines the direction, 122 | // where 0 = OUT and 1 = IN. The libusb C.LIBUSB_ENDPOINT_IN enumeration is 123 | // 128 instead of 1. Therefore, I'm not using C.LIBUSB_ENDPOINT_IN (128). 124 | endpointOut EndpointDirection = C.LIBUSB_ENDPOINT_OUT 125 | endpointIn EndpointDirection = 1 126 | directionMask endpointAddress = 0x80 127 | directionBit = 7 128 | ) 129 | 130 | var endpointDirections = map[EndpointDirection]string{ 131 | endpointOut: "Out: host-to-device.", 132 | endpointIn: "In: device-to-host.", 133 | } 134 | 135 | // String implements the Stringer interface for endpointDirection. 136 | func (endpointDirection EndpointDirection) String() string { 137 | return endpointDirections[endpointDirection] 138 | } 139 | 140 | // TransferType provides which type of transfer. 141 | type TransferType int 142 | 143 | // Endpoint transfer type http://bit.ly/enum_libusb_transfer_type 144 | const ( 145 | ControlTransfer TransferType = C.LIBUSB_TRANSFER_TYPE_CONTROL 146 | IsochronousTransfer TransferType = C.LIBUSB_TRANSFER_TYPE_ISOCHRONOUS 147 | BulkTransfer TransferType = C.LIBUSB_TRANSFER_TYPE_BULK 148 | InterruptTransfer TransferType = C.LIBUSB_TRANSFER_TYPE_INTERRUPT 149 | ) 150 | 151 | var transferTypes = map[TransferType]string{ 152 | ControlTransfer: "Control endpoint.", 153 | IsochronousTransfer: "Isochronous endpoint.", 154 | BulkTransfer: "Bulk endpoint.", 155 | InterruptTransfer: "Interrupt endpoint.", 156 | } 157 | 158 | func (transferType TransferType) String() string { 159 | return transferTypes[transferType] 160 | } 161 | 162 | // TODO(mdr): May want to replace uint8 with a type specific for indexes. 163 | 164 | type synchronizationType byte 165 | 166 | // Synchronization type for isochronous endpoints. "Values for bits 2:3 of the 167 | // bmAttributes field in libusb_endpoint_descriptor" 168 | // http://bit.ly/enum_libusb_iso_sync_type 169 | const ( 170 | IsoSyncTypeNone synchronizationType = C.LIBUSB_ISO_SYNC_TYPE_NONE 171 | IsoSyncTypeAsync synchronizationType = C.LIBUSB_ISO_SYNC_TYPE_ASYNC 172 | IsoSyncTypeAdaptive synchronizationType = C.LIBUSB_ISO_SYNC_TYPE_ADAPTIVE 173 | IsoSynceTypeSync synchronizationType = C.LIBUSB_ISO_SYNC_TYPE_SYNC 174 | ) 175 | -------------------------------------------------------------------------------- /descriptors_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2025 The libusb developers. All rights reserved. 2 | // Project site: https://github.com/gotmc/libusb 3 | // Use of this source code is governed by a MIT-style license that 4 | // can be found in the LICENSE.txt file for the project. 5 | 6 | package libusb 7 | 8 | import ( 9 | "testing" 10 | ) 11 | 12 | const ( 13 | failCheck = `✗` // UTF-8 u2717 14 | passCheck = `✓` // UTF-8 u2713 15 | ) 16 | 17 | func TestBcdType(t *testing.T) { 18 | testCases := []struct { 19 | bcdValue uint16 20 | bcdString string 21 | bcdDecimal float64 22 | }{ 23 | {0x0300, "0x0300 (3.00)", 3.0}, 24 | {0x0200, "0x0200 (2.00)", 2.0}, 25 | {0x0110, "0x0110 (1.10)", 1.1}, 26 | {0x0100, "0x0100 (1.00)", 1.0}, 27 | } 28 | t.Log("Given the need to test the bcd type") 29 | { 30 | for _, testCase := range testCases { 31 | b := bcd(testCase.bcdValue) 32 | t.Logf("\tWhen getting the string for bcd %#04x", testCase.bcdValue) 33 | computedString := b.String() 34 | if computedString != testCase.bcdString { 35 | t.Errorf( 36 | "\t%v Should have computed %s but got %s", 37 | failCheck, 38 | testCase.bcdString, 39 | computedString, 40 | ) 41 | } else { 42 | t.Logf("\t%v Should compute %s", passCheck, computedString) 43 | } 44 | } 45 | } 46 | } 47 | 48 | func TestClassCodeStringMethod(t *testing.T) { 49 | testCases := []struct { 50 | class classCode 51 | expected string 52 | }{ 53 | { 54 | perInterface, 55 | "Each interface specifies its own class information and all interfaces operate independently.", 56 | }, 57 | {audio, "Audio class."}, 58 | {comm, "Communications class."}, 59 | {hid, "Human Interface Device class."}, 60 | {physical, "Physical."}, 61 | {printer, "Printer class."}, 62 | {image, "Image class."}, 63 | {massStorage, "Mass storage class."}, 64 | {hub, "Hub class."}, 65 | {data, "Data class."}, 66 | {smartCard, "Smart Card."}, 67 | {contentSecurity, "Content Security."}, 68 | {video, "Video."}, 69 | {personalHealthcare, "Personal Healthcare."}, 70 | {diagnosticDevice, "Diagnostic Device."}, 71 | {wireless, "Wireless class."}, 72 | {application, "Application class."}, 73 | {vendorSpec, "Class is vendor-specific."}, 74 | } 75 | t.Log("Given the need to test the classCode.String() method") 76 | { 77 | for _, testCase := range testCases { 78 | t.Logf("\tWhen getting classCode %d's string", testCase.class) 79 | computed := testCase.class.String() 80 | if computed != testCase.expected { 81 | t.Errorf( 82 | "\t%v Should have yielded: %s, but got %s", 83 | failCheck, 84 | testCase.expected, 85 | computed, 86 | ) 87 | } else { 88 | t.Logf("\t%v Should yield: %s", passCheck, computed) 89 | } 90 | } 91 | } 92 | } 93 | 94 | func TestDescriptortypeStringMethod(t *testing.T) { 95 | testCases := []struct { 96 | desc descriptorType 97 | expected string 98 | }{ 99 | {descDevice, "Device descriptor."}, 100 | {descConfig, "Configuration descriptor."}, 101 | {descString, "String descriptor."}, 102 | {descInterface, "Interface descriptor."}, 103 | {descEndpoint, "Endpoint descriptor."}, 104 | {descBos, "BOS descriptor."}, 105 | {descDeviceCapability, "Device Capability descriptor."}, 106 | {descHid, "HID descriptor."}, 107 | {descReport, "HID report descriptor."}, 108 | {descPhysical, "Physical descriptor."}, 109 | {descHub, "Hub descriptor."}, 110 | {descSuperspeedHub, "SuperSpeed Hub descriptor."}, 111 | {descEndpointCompanion, "SuperSpeed Endpoint Companion descriptor."}, 112 | } 113 | t.Log("Given the need to test the descriptorType.String() method") 114 | { 115 | for _, testCase := range testCases { 116 | t.Logf("\tWhen getting descriptorType %d's string", testCase.desc) 117 | computed := testCase.desc.String() 118 | if computed != testCase.expected { 119 | t.Errorf( 120 | "\t%v Should have yielded: %s, but got %s", 121 | failCheck, 122 | testCase.expected, 123 | computed, 124 | ) 125 | } else { 126 | t.Logf("\t%v Should yield: %s", passCheck, computed) 127 | } 128 | } 129 | } 130 | } 131 | 132 | func TestEndpointDirectionStringMethod(t *testing.T) { 133 | testCases := []struct { 134 | end EndpointDirection 135 | expected string 136 | }{ 137 | {endpointOut, "Out: host-to-device."}, 138 | {endpointIn, "In: device-to-host."}, 139 | } 140 | t.Log("Given the need to test the endpointDirection.String() method") 141 | { 142 | for _, testCase := range testCases { 143 | t.Logf("\tWhen getting endpointDirection %d's string", testCase.end) 144 | computed := testCase.end.String() 145 | if computed != testCase.expected { 146 | t.Errorf( 147 | "\t%v Should have yielded: %s, but got %s", 148 | failCheck, 149 | testCase.expected, 150 | computed, 151 | ) 152 | } else { 153 | t.Logf("\t%v Should yield: %s", passCheck, computed) 154 | } 155 | } 156 | } 157 | } 158 | 159 | func TestTransferTypeStringMethod(t *testing.T) { 160 | testCases := []struct { 161 | transfer TransferType 162 | expected string 163 | }{ 164 | {ControlTransfer, "Control endpoint."}, 165 | {IsochronousTransfer, "Isochronous endpoint."}, 166 | {BulkTransfer, "Bulk endpoint."}, 167 | {InterruptTransfer, "Interrupt endpoint."}, 168 | } 169 | t.Log("Given the need to test the endpointDirection.String() method") 170 | { 171 | for _, testCase := range testCases { 172 | t.Logf("\tWhen getting endpointDirection %d's string", testCase.transfer) 173 | computed := testCase.transfer.String() 174 | if computed != testCase.expected { 175 | t.Errorf( 176 | "\t%v Should have yielded: %s, but got %s", 177 | failCheck, 178 | testCase.expected, 179 | computed, 180 | ) 181 | } else { 182 | t.Logf("\t%v Should yield: %s", passCheck, computed) 183 | } 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /device.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2025 The libusb developers. All rights reserved. 2 | // Project site: https://github.com/gotmc/libusb 3 | // Use of this source code is governed by a MIT-style license that 4 | // can be found in the LICENSE.txt file for the project. 5 | 6 | package libusb 7 | 8 | // #cgo pkg-config: libusb-1.0 9 | // #include 10 | import "C" 11 | import ( 12 | "fmt" 13 | "unsafe" 14 | ) 15 | 16 | // TODO(mdr): Do I need to be handling the reference counts in cgo? 17 | 18 | // Device represents a USB device including the opaque libusb_device struct. 19 | type Device struct { 20 | libusbDevice *C.libusb_device 21 | ActiveConfiguration *ConfigDescriptor 22 | } 23 | 24 | // Descriptor represents a USB device descriptor as a Go struct. 25 | type Descriptor struct { 26 | Length uint8 27 | DescriptorType descriptorType 28 | USBSpecification bcd 29 | DeviceClass classCode 30 | DeviceSubClass byte 31 | DeviceProtocol byte 32 | MaxPacketSize0 uint8 33 | VendorID uint16 34 | ProductID uint16 35 | DeviceReleaseNumber bcd 36 | ManufacturerIndex uint8 37 | ProductIndex uint8 38 | SerialNumberIndex uint8 39 | NumConfigurations uint8 40 | } 41 | 42 | // BusNumber gets "the number of the bus that a device is connected to." 43 | // (Source: libusb docs) 44 | func (dev *Device) BusNumber() (int, error) { 45 | busNumber, err := C.libusb_get_bus_number(dev.libusbDevice) 46 | if err != nil { 47 | return 0, err 48 | } 49 | return int(busNumber), nil 50 | } 51 | 52 | // PortNumber gets "the number of the port that a device is connected to. 53 | // Unless the OS does something funky, or you are hot-plugging USB extension 54 | // cards, the port number returned by this call is usually guaranteed to be 55 | // uniquely tied to a physical port, meaning that different devices plugged on 56 | // the same physical port should return the same port number. But outside of 57 | // this, there is no guarantee that the port number returned by this call will 58 | // remain the same, or even match the order in which ports have been numbered 59 | // by the HUB/HCD manufacturer." (Source: libusb docs) 60 | func (dev *Device) PortNumber() (int, error) { 61 | portNumber, err := C.libusb_get_port_number(dev.libusbDevice) 62 | if err != nil { 63 | return 0, fmt.Errorf("port number is unavailable for device %v", dev) 64 | } 65 | return int(portNumber), nil 66 | } 67 | 68 | // MaxPacketSize is a "convenience function to retrieve the wMaxPacketSize 69 | // value for a particular endpoint in the active device configuration. This 70 | // function was originally intended to be of assistance when setting up 71 | // isochronous transfers, but a design mistake resulted in this function 72 | // instead. It simply returns the wMaxPacketSize value without considering its 73 | // contents. If you're dealing with isochronous transfers, you probably want 74 | // libusb_get_max_iso_packet_size() instead." (Source: libusb docs) 75 | func (dev *Device) MaxPacketSize(ep endpointAddress) (int, error) { 76 | maxPacketSize, err := C.libusb_get_max_packet_size(dev.libusbDevice, C.uchar(ep)) 77 | if err != nil { 78 | return 0, fmt.Errorf("wMaxPacketSize is unavailable for device %v", dev) 79 | } 80 | return int(maxPacketSize), nil 81 | } 82 | 83 | // DeviceAddress gets "the address of the device on the bus it is connected 84 | // to." (Source: libusb docs) 85 | func (dev *Device) DeviceAddress() (int, error) { 86 | deviceAddress, err := C.libusb_get_device_address(dev.libusbDevice) 87 | if err != nil { 88 | return 0, err 89 | } 90 | return int(deviceAddress), nil 91 | } 92 | 93 | // Speed gets "the negotiated connection speed for a device." (Source: 94 | // libusb docs) 95 | func (dev *Device) Speed() (SpeedType, error) { 96 | deviceSpeed, err := C.libusb_get_device_speed(dev.libusbDevice) 97 | if err != nil { 98 | return 0, err 99 | } 100 | return SpeedType(deviceSpeed), nil 101 | } 102 | 103 | // Open will "open a device and obtain a device handle. A handle allows you to 104 | // perform I/O on the device in question. Internally, this function adds a 105 | // reference to the device and makes it available to you through 106 | // libusb_get_device(). This reference is removed during libusb_close()." This 107 | // is a non-blocking function; no requests are sent over the bus. (Source: 108 | // libusb docs) 109 | func (dev *Device) Open() (*DeviceHandle, error) { 110 | var handle *C.libusb_device_handle 111 | err := C.libusb_open(dev.libusbDevice, &handle) 112 | if err != 0 { 113 | return nil, ErrorCode(err) 114 | } 115 | deviceHandle := &DeviceHandle{ 116 | libusbDeviceHandle: handle, 117 | } 118 | return deviceHandle, nil 119 | } 120 | 121 | // DeviceDescriptor implements the libusb_get_device_descriptor function to 122 | // update the DeviceDescriptor struct embedded in the Device. DeviceDescriptor 123 | // gets "the USB device descriptor for a given device. This is a non-blocking 124 | // function; the device descriptor is cached in memory. Note since 125 | // libusb-1.0.16, LIBUSB_API_VERSION >= 0x01000102, this function always 126 | // succeeds." (Source: libusb docs) 127 | func (dev *Device) DeviceDescriptor() (*Descriptor, error) { 128 | var desc C.struct_libusb_device_descriptor 129 | err := C.libusb_get_device_descriptor(dev.libusbDevice, &desc) 130 | if err != 0 { 131 | return nil, ErrorCode(err) 132 | } 133 | deviceDescriptor := Descriptor{ 134 | Length: uint8(desc.bLength), 135 | DescriptorType: descriptorType(desc.bDescriptorType), 136 | USBSpecification: bcd(desc.bcdUSB), 137 | DeviceClass: classCode(desc.bDeviceClass), 138 | DeviceSubClass: byte(desc.bDeviceSubClass), 139 | DeviceProtocol: byte(desc.bDeviceProtocol), 140 | MaxPacketSize0: uint8(desc.bMaxPacketSize0), 141 | VendorID: uint16(desc.idVendor), 142 | ProductID: uint16(desc.idProduct), 143 | DeviceReleaseNumber: bcd(desc.bcdDevice), 144 | ManufacturerIndex: uint8(desc.iManufacturer), 145 | ProductIndex: uint8(desc.iProduct), 146 | SerialNumberIndex: uint8(desc.iSerialNumber), 147 | NumConfigurations: uint8(desc.bNumConfigurations), 148 | } 149 | return &deviceDescriptor, nil 150 | } 151 | 152 | // ActiveConfigDescriptor "gets the USB configuration descriptor for the 153 | // currently active configuration. This is a non-blocking function which does 154 | // not involve any requests being sent to the device." (Source: libusb docs) 155 | func (dev *Device) ActiveConfigDescriptor() (*ConfigDescriptor, error) { 156 | var config *C.struct_libusb_config_descriptor 157 | err := C.libusb_get_active_config_descriptor(dev.libusbDevice, &config) 158 | defer C.libusb_free_config_descriptor(config) 159 | if err != 0 { 160 | return nil, ErrorCode(err) 161 | } 162 | activeConfiguration := &ConfigDescriptor{ 163 | Length: int(config.bLength), 164 | DescriptorType: descriptorType(config.bDescriptorType), 165 | TotalLength: uint16(config.wTotalLength), 166 | NumInterfaces: int(config.bNumInterfaces), 167 | ConfigurationValue: uint8(config.bConfigurationValue), 168 | ConfigurationIndex: uint8(config.iConfiguration), 169 | Attributes: uint8(config.bmAttributes), 170 | MaxPowerMilliAmperes: 2 * uint(config.MaxPower), // Convert from 2 mA to just mA 171 | SupportedInterfaces: nil, 172 | } 173 | var cInterface *C.struct_libusb_interface = config._interface 174 | length := activeConfiguration.NumInterfaces 175 | libusbInterfaces := unsafe.Slice(cInterface, length) 176 | // hdr := reflect.SliceHeader{ 177 | // Data: uintptr(unsafe.Pointer(cInterface)), 178 | // Len: length, 179 | // Cap: length, 180 | // } 181 | // libusbInterfaces := *(*[]C.struct_libusb_interface)(unsafe.Pointer(&hdr)) 182 | 183 | var supportedInterfaces SupportedInterfaces 184 | // Loop through the array of interfaces support by this configuration 185 | // const struct libusb_interface * interface 186 | for _, libusbInterface := range libusbInterfaces { 187 | supportedInterface := SupportedInterface{ 188 | NumAltSettings: int(libusbInterface.num_altsetting), 189 | InterfaceDescriptors: nil, 190 | } 191 | var interfaceDescriptors InterfaceDescriptors 192 | var cInterfaceDescriptor *C.struct_libusb_interface_descriptor = libusbInterface.altsetting 193 | length := int(libusbInterface.num_altsetting) 194 | libusbInterfaceDescriptors := unsafe.Slice(cInterfaceDescriptor, length) 195 | // hdr := reflect.SliceHeader{ 196 | // Data: uintptr(unsafe.Pointer(cInterfaceDescriptor)), 197 | // Len: length, 198 | // Cap: length, 199 | // } 200 | // libusbInterfaceDescriptors := *(*[]C.struct_libusb_interface_descriptor)(unsafe.Pointer(&hdr)) 201 | 202 | // Loop through the array of interface descriptors 203 | // const struct libusb_interface_descriptor * altsetting 204 | for _, libusbInterfaceDescriptor := range libusbInterfaceDescriptors { 205 | interfaceDescriptor := InterfaceDescriptor{ 206 | Length: int(libusbInterfaceDescriptor.bLength), 207 | DescriptorType: descriptorType(libusbInterfaceDescriptor.bDescriptorType), 208 | InterfaceNumber: int(libusbInterfaceDescriptor.bInterfaceNumber), 209 | AlternateSetting: int(libusbInterfaceDescriptor.bAlternateSetting), 210 | NumEndpoints: int(libusbInterfaceDescriptor.bNumEndpoints), 211 | InterfaceClass: uint8(libusbInterfaceDescriptor.bInterfaceClass), 212 | InterfaceSubClass: uint8(libusbInterfaceDescriptor.bInterfaceSubClass), 213 | InterfaceProtocol: uint8(libusbInterfaceDescriptor.bInterfaceProtocol), 214 | InterfaceIndex: int(libusbInterfaceDescriptor.iInterface), 215 | EndpointDescriptors: nil, 216 | } 217 | var endpointDescriptors EndpointDescriptors 218 | var cEndpointDescriptor *C.struct_libusb_endpoint_descriptor = libusbInterfaceDescriptor.endpoint 219 | length := int(libusbInterfaceDescriptor.bNumEndpoints) 220 | libusbEndpointDescriptors := unsafe.Slice(cEndpointDescriptor, length) 221 | // hdr := reflect.SliceHeader{ 222 | // Data: uintptr(unsafe.Pointer(cEndpointDescriptor)), 223 | // Len: length, 224 | // Cap: length, 225 | // } 226 | 227 | // libusbEndpointDescriptors := *(*[]C.struct_libusb_endpoint_descriptor)(unsafe.Pointer(&hdr)) 228 | 229 | // Loop through the array of endpoint descriptors 230 | // const struct libusb_endpoint_descriptor * endpoint 231 | for _, libusbEndpointDescriptor := range libusbEndpointDescriptors { 232 | endpointDescriptor := EndpointDescriptor{ 233 | Length: int(libusbEndpointDescriptor.bLength), 234 | DescriptorType: descriptorType(libusbEndpointDescriptor.bDescriptorType), 235 | EndpointAddress: endpointAddress(libusbEndpointDescriptor.bEndpointAddress), 236 | Attributes: endpointAttributes(libusbEndpointDescriptor.bmAttributes), 237 | MaxPacketSize: uint16(libusbEndpointDescriptor.wMaxPacketSize), 238 | Interval: uint8(libusbEndpointDescriptor.bInterval), 239 | } 240 | endpointDescriptors = append(endpointDescriptors, &endpointDescriptor) 241 | } 242 | interfaceDescriptor.EndpointDescriptors = endpointDescriptors 243 | interfaceDescriptors = append(interfaceDescriptors, &interfaceDescriptor) 244 | } 245 | supportedInterface.InterfaceDescriptors = interfaceDescriptors 246 | supportedInterfaces = append(supportedInterfaces, &supportedInterface) 247 | } 248 | activeConfiguration.SupportedInterfaces = supportedInterfaces 249 | return activeConfiguration, nil 250 | } 251 | 252 | // ConfigDescriptor "gets a USB configuration descriptor based on its index. 253 | // This is a non-blocking function which does not involve any requests being 254 | // sent to the device." (Source: libusb docs) 255 | func (dev *Device) ConfigDescriptor(configIndex int) (*ConfigDescriptor, error) { 256 | var cConfig *C.struct_libusb_config_descriptor 257 | err := C.libusb_get_config_descriptor(dev.libusbDevice, C.uint8_t(configIndex), &cConfig) 258 | defer C.libusb_free_config_descriptor(cConfig) 259 | if err != 0 { 260 | return nil, ErrorCode(err) 261 | } 262 | configuration := &ConfigDescriptor{ 263 | Length: int(cConfig.bLength), 264 | DescriptorType: descriptorType(cConfig.bDescriptorType), 265 | TotalLength: uint16(cConfig.wTotalLength), 266 | NumInterfaces: int(cConfig.bNumInterfaces), 267 | ConfigurationValue: uint8(cConfig.bConfigurationValue), 268 | ConfigurationIndex: uint8(cConfig.iConfiguration), 269 | Attributes: uint8(cConfig.bmAttributes), 270 | MaxPowerMilliAmperes: 2 * uint(cConfig.MaxPower), // Convert from 2 mA to just mA 271 | SupportedInterfaces: nil, 272 | } 273 | return configuration, nil 274 | } 275 | 276 | // ConfigDescriptorByValue gets "a USB configuration descriptor with a 277 | // specific bConfigurationValue. This is a non-blocking function which does not 278 | // involve any requests being sent to the device. (Source: libusb docs) 279 | func (dev *Device) ConfigDescriptorByValue(configValue int) (*ConfigDescriptor, error) { 280 | var cConfig *C.struct_libusb_config_descriptor 281 | err := C.libusb_get_config_descriptor_by_value( 282 | dev.libusbDevice, C.uint8_t(configValue), &cConfig, 283 | ) 284 | defer C.libusb_free_config_descriptor(cConfig) 285 | if err != 0 { 286 | return nil, ErrorCode(err) 287 | } 288 | configuration := &ConfigDescriptor{ 289 | Length: int(cConfig.bLength), 290 | DescriptorType: descriptorType(cConfig.bDescriptorType), 291 | TotalLength: uint16(cConfig.wTotalLength), 292 | NumInterfaces: int(cConfig.bNumInterfaces), 293 | ConfigurationValue: uint8(cConfig.bConfigurationValue), 294 | ConfigurationIndex: uint8(cConfig.iConfiguration), 295 | Attributes: uint8(cConfig.bmAttributes), 296 | MaxPowerMilliAmperes: 2 * uint(cConfig.MaxPower), // Convert from 2 mA to just mA 297 | SupportedInterfaces: nil, 298 | } 299 | return configuration, nil 300 | } 301 | 302 | // FindInterfacesByClass finds all interfaces that match the given USB class code. 303 | // This is particularly useful for devices where the device class is reported as 304 | // LIBUSB_CLASS_PER_INTERFACE (0), where individual interfaces have their own class codes. 305 | // 306 | // For example, to find all printer class interfaces: 307 | // 308 | // printerInterfaces, err := device.FindInterfacesByClass(libusb.InterfaceClassPrinter) 309 | // 310 | // Example of finding and printing information about a printer device: 311 | // 312 | // package main 313 | // 314 | // import ( 315 | // "fmt" 316 | // "github.com/gotmc/libusb/v2" 317 | // ) 318 | // 319 | // func main() { 320 | // ctx, _ := libusb.NewContext() 321 | // devices, _ := ctx.DeviceList() 322 | // 323 | // for _, device := range devices { 324 | // desc, _ := device.DeviceDescriptor() 325 | // // Check if this is a per-interface class device 326 | // if desc.DeviceClass == 0 { 327 | // // Find printer interfaces (class 7) 328 | // printerIfaces, _ := device.FindInterfacesByClass(libusb.InterfaceClassPrinter) 329 | // if len(printerIfaces) > 0 { 330 | // fmt.Printf("Found printer device: VID=0x%04x, PID=0x%04x\n", desc.VendorID, desc.ProductID) 331 | // } 332 | // } 333 | // } 334 | // } 335 | func (dev *Device) FindInterfacesByClass(class uint8) (InterfaceDescriptors, error) { 336 | config, err := dev.ActiveConfigDescriptor() 337 | if err != nil { 338 | return nil, err 339 | } 340 | return config.SupportedInterfaces.GetAllInterfacesByClass(class), nil 341 | } 342 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2025 The libusb developers. All rights reserved. 2 | // Project site: https://github.com/gotmc/libusb 3 | // Use of this source code is governed by a MIT-style license that 4 | // can be found in the LICENSE.txt file for the project. 5 | 6 | /* 7 | Package libusb provides Go bindings for the libusb 1.0 C library. 8 | */ 9 | package libusb 10 | -------------------------------------------------------------------------------- /endpoint.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2025 The libusb developers. All rights reserved. 2 | // Project site: https://github.com/gotmc/libusb 3 | // Use of this source code is governed by a MIT-style license that 4 | // can be found in the LICENSE.txt file for the project. 5 | 6 | package libusb 7 | 8 | // #cgo pkg-config: libusb-1.0 9 | // #include 10 | import "C" 11 | 12 | // Endpoint doesn't seem to model anything. Did I replace this with 13 | // EndpointDescriptor? 14 | type Endpoint struct { 15 | // FIXME(mdr): Is this needed/used? Can this safely be deleted? 16 | } 17 | 18 | type endpointAddress byte 19 | type endpointAttributes byte 20 | 21 | // EndpointDescriptor models the descriptor for a given endpoint. 22 | type EndpointDescriptor struct { 23 | Length int 24 | DescriptorType descriptorType 25 | EndpointAddress endpointAddress 26 | Attributes endpointAttributes 27 | MaxPacketSize uint16 28 | Interval uint8 29 | Refresh uint8 30 | SynchAddress uint8 31 | } 32 | 33 | // EndpointDescriptors contains the available endpoint descriptors. 34 | type EndpointDescriptors []*EndpointDescriptor 35 | 36 | // Direction returns the endpointDirection. 37 | func (end *EndpointDescriptor) Direction() EndpointDirection { 38 | // FIXME(mdr): Is this funciton needed? What purpose does it serve? If I'm 39 | // keeping it, I should not return an unexported type. 40 | return end.EndpointAddress.direction() 41 | } 42 | 43 | // Number returns the endpoint number in bits 0..3 in the endpoint 44 | // address. 45 | func (end *EndpointDescriptor) Number() byte { 46 | return end.EndpointAddress.endpointNumber() 47 | } 48 | 49 | // TransferType returns the transfer type for an endpoint. 50 | func (end *EndpointDescriptor) TransferType() TransferType { 51 | // FIXME(mdr): Is this funciton needed? What purpose does it serve? If I'm 52 | // keeping it, I should not return an unexported type. 53 | return end.Attributes.transferType() 54 | } 55 | 56 | func (address endpointAddress) direction() EndpointDirection { 57 | // Bit 7 of the endpointAddress determines the direction 58 | const directionMask = 0x80 59 | const directionBit = 7 60 | return EndpointDirection(address&directionMask) >> directionBit 61 | } 62 | 63 | func (address endpointAddress) endpointNumber() byte { 64 | // Bits 0..3 determine the endpoint number 65 | const endpointNumberMask = 0x0F 66 | return byte(address & endpointNumberMask) 67 | } 68 | 69 | func (attributes endpointAttributes) transferType() TransferType { 70 | // Bits 0..1 of the bmAttributes determines the transfer type 71 | const transferTypeMask = 0x03 72 | return TransferType(attributes & transferTypeMask) 73 | } 74 | -------------------------------------------------------------------------------- /examples/control_transfer/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gotmc/libusb/examples/control_transfer 2 | 3 | go 1.21 4 | 5 | require github.com/gotmc/libusb/v2 v2.0.0 6 | 7 | replace github.com/gotmc/libusb/v2 => ../.. 8 | 9 | -------------------------------------------------------------------------------- /examples/control_transfer/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2025 The libusb developers. All rights reserved. 2 | // Project site: https://github.com/gotmc/libusb 3 | // Use of this source code is governed by a MIT-style license that 4 | // can be found in the LICENSE.txt file for the project. 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "log" 11 | 12 | "github.com/gotmc/libusb/v2" 13 | ) 14 | 15 | func main() { 16 | ctx, err := libusb.NewContext() 17 | if err != nil { 18 | log.Fatalf("Error creating USB context: %v", err) 19 | } 20 | defer ctx.Close() 21 | 22 | // For demo purposes only - replace with your actual values 23 | var vendorID uint16 = 0x1234 24 | var productID uint16 = 0x5678 25 | 26 | // Open device 27 | device, handle, err := ctx.OpenDeviceWithVendorProduct(vendorID, productID) 28 | if err != nil { 29 | log.Printf("Could not open device %04x:%04x, using example code only", vendorID, productID) 30 | // Continue with example code anyway 31 | } else { 32 | defer handle.Close() 33 | log.Printf("Opened device %04x:%04x", vendorID, productID) 34 | } 35 | 36 | // Example 1: Using the original approach with raw byte 37 | // This requires bit manipulation and can be error-prone 38 | fmt.Println("\nExample 1: Original approach with raw bytes") 39 | fmt.Println("----------------------------------------") 40 | 41 | // Construct requestType using bit manipulation: Class request (0x01<<5) to interface (0x01) 42 | rawRequestType := byte((0x01 << 5) + 0x01) 43 | fmt.Printf("Raw requestType: 0x%02x\n", rawRequestType) 44 | 45 | // Example code - would actually be executed if handle != nil 46 | fmt.Printf("handle.ControlTransfer(0x%02x, 0x09, 0x0300, 0, data, len, 5000)\n", rawRequestType) 47 | 48 | // Example 2: Using the new type-safe approach with constants 49 | fmt.Println("\nExample 2: Type-safe approach with exported types") 50 | fmt.Println("----------------------------------------") 51 | 52 | // Use library constants for better readability and type safety 53 | direction := libusb.HostToDevice 54 | reqType := libusb.Class 55 | recipient := libusb.InterfaceRecipient 56 | 57 | fmt.Printf("Direction: %v\n", direction) 58 | fmt.Printf("RequestType: %v\n", reqType) 59 | fmt.Printf("Recipient: %v\n", recipient) 60 | 61 | // Create combined requestType using BitmapRequestType 62 | bmRequestType := libusb.BitmapRequestType(direction, reqType, recipient) 63 | fmt.Printf("Combined bmRequestType: 0x%02x\n", bmRequestType) 64 | 65 | // Example code 66 | fmt.Printf("handle.ControlTransferWithTypes(%v, %v, %v, 0x09, 0x0300, 0, data, len, 5000)\n", 67 | direction, reqType, recipient) 68 | 69 | // Example 3: Using the new helper methods 70 | fmt.Println("\nExample 3: Using convenience helper methods") 71 | fmt.Println("----------------------------------------") 72 | 73 | fmt.Printf("handle.ControlOut(%v, %v, 0x09, 0x0300, 0, data, 5000)\n", 74 | reqType, recipient) 75 | } -------------------------------------------------------------------------------- /examples/find_printer/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gotmc/libusb/examples/find_printer 2 | 3 | go 1.21 4 | 5 | require github.com/gotmc/libusb/v2 v2.0.0 6 | 7 | replace github.com/gotmc/libusb/v2 => ../.. 8 | 9 | -------------------------------------------------------------------------------- /examples/find_printer/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2025 The libusb developers. All rights reserved. 2 | // Project site: https://github.com/gotmc/libusb 3 | // Use of this source code is governed by a MIT-style license that 4 | // can be found in the LICENSE.txt file for the project. 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "log" 11 | 12 | "github.com/gotmc/libusb/v2" 13 | ) 14 | 15 | func main() { 16 | // Create a new libusb context 17 | ctx, err := libusb.NewContext() 18 | if err != nil { 19 | log.Fatalf("Error creating context: %s", err) 20 | } 21 | defer ctx.Close() 22 | 23 | // Get list of devices 24 | devices, err := ctx.DeviceList() 25 | if err != nil { 26 | log.Fatalf("Error getting device list: %s", err) 27 | } 28 | defer devices.Free() 29 | 30 | fmt.Printf("Found %d USB devices\n", len(devices)) 31 | 32 | // Loop through all devices 33 | for i, device := range devices { 34 | desc, err := device.DeviceDescriptor() 35 | if err != nil { 36 | log.Printf("Error getting device descriptor for device %d: %s", i, err) 37 | continue 38 | } 39 | 40 | // Print the device class information 41 | fmt.Printf("\nDevice %d: VID=0x%04x, PID=0x%04x\n", i, desc.VendorID, desc.ProductID) 42 | fmt.Printf(" Device Class: %s (0x%02x)\n", desc.DeviceClass, byte(desc.DeviceClass)) 43 | 44 | // If this is a per-interface class device, inspect the interfaces 45 | if desc.DeviceClass == libusb.perInterface { 46 | fmt.Println(" This device uses per-interface class codes. Checking interfaces...") 47 | 48 | // Find printer interfaces (class 7) 49 | printerIfaces, err := device.FindInterfacesByClass(libusb.InterfaceClassPrinter) 50 | if err != nil { 51 | log.Printf(" Error getting interface info: %s", err) 52 | continue 53 | } 54 | 55 | if len(printerIfaces) > 0 { 56 | fmt.Printf(" Found %d printer interface(s)!\n", len(printerIfaces)) 57 | 58 | // Print details about each printer interface 59 | for j, iface := range printerIfaces { 60 | fmt.Printf(" Printer Interface %d:\n", j) 61 | fmt.Printf(" Interface Number: %d\n", iface.InterfaceNumber) 62 | fmt.Printf(" Interface Class: 0x%02x (Printer)\n", iface.InterfaceClass) 63 | fmt.Printf(" Interface SubClass: 0x%02x\n", iface.InterfaceSubClass) 64 | fmt.Printf(" Interface Protocol: 0x%02x\n", iface.InterfaceProtocol) 65 | fmt.Printf(" Endpoints: %d\n", iface.NumEndpoints) 66 | } 67 | } 68 | } 69 | } 70 | } 71 | 72 | -------------------------------------------------------------------------------- /examples/get_sn/get_sn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gotmc/libusb/8b1f3e9b30a39139e64d20ab486dc7a95b1a99ca/examples/get_sn/get_sn -------------------------------------------------------------------------------- /examples/get_sn/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gotmc/libusb/examples/get_sn 2 | 3 | go 1.21 4 | 5 | require github.com/gotmc/libusb/v2 v2.3.0 6 | 7 | replace github.com/gotmc/libusb/v2 => ../../ 8 | -------------------------------------------------------------------------------- /examples/get_sn/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015–2025 The libusb developers. All rights reserved. 2 | // Project site: https://github.com/gotmc/libusb 3 | // Use of this source code is governed by a MIT-style license that 4 | // can be found in the LICENSE.txt file for the project. 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "log" 11 | 12 | libusb "github.com/gotmc/libusb/v2" 13 | ) 14 | 15 | func showVersion() { 16 | version := libusb.Version() 17 | fmt.Printf( 18 | "Using libusb version %d.%d.%d (%d)\n", 19 | version.Major, 20 | version.Minor, 21 | version.Micro, 22 | version.Nano, 23 | ) 24 | } 25 | 26 | func main() { 27 | showVersion() 28 | ctx, err := libusb.NewContext() 29 | if err != nil { 30 | log.Fatal("Couldn't create USB context. Ending now.") 31 | } 32 | defer ctx.Close() 33 | devices, err := ctx.DeviceList() 34 | if err != nil { 35 | log.Fatalf("Couldn't get devices") 36 | } 37 | log.Printf("Found %v USB devices.\n", len(devices)) 38 | for _, device := range devices { 39 | usbDeviceDescriptor, err := device.DeviceDescriptor() 40 | if err != nil { 41 | log.Printf("Error getting device descriptor: %s", err) 42 | continue 43 | } 44 | addr, err := device.DeviceAddress() 45 | if err != nil { 46 | log.Printf("Error getting device address: %s", err) 47 | continue 48 | } 49 | log.Printf("Device address: %d", addr) 50 | handle, err := device.Open() 51 | if err != nil { 52 | log.Printf("Error opening device: %s", err) 53 | continue 54 | } 55 | defer handle.Close() 56 | serialNumber, err := handle.StringDescriptorASCII(usbDeviceDescriptor.SerialNumberIndex) 57 | if err != nil { 58 | serialNumber = "N/A" 59 | } 60 | manufacturer, err := handle.StringDescriptorASCII(usbDeviceDescriptor.ManufacturerIndex) 61 | if err != nil { 62 | manufacturer = "N/A" 63 | } 64 | product, err := handle.StringDescriptorASCII(usbDeviceDescriptor.ProductIndex) 65 | if err != nil { 66 | product = "N/A" 67 | } 68 | log.Printf("Found %s (S/N: %s) manufactured by %s", 69 | product, 70 | serialNumber, 71 | manufacturer, 72 | ) 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /examples/get_sn_lite/get_sn_lite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gotmc/libusb/8b1f3e9b30a39139e64d20ab486dc7a95b1a99ca/examples/get_sn_lite/get_sn_lite -------------------------------------------------------------------------------- /examples/get_sn_lite/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gotmc/libusb/examples/get_sn_lite 2 | 3 | go 1.21 4 | 5 | require github.com/gotmc/libusb/v2 v2.3.0 6 | 7 | replace github.com/gotmc/libusb/v2 => ../../ 8 | -------------------------------------------------------------------------------- /examples/get_sn_lite/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015–2025 The libusb developers. All rights reserved. 2 | // Project site: https://github.com/gotmc/libusb 3 | // Use of this source code is governed by a MIT-style license that 4 | // can be found in the LICENSE.txt file for the project. 5 | 6 | package main 7 | 8 | import ( 9 | "log" 10 | 11 | libusb "github.com/gotmc/libusb/v2" 12 | ) 13 | 14 | func main() { 15 | ctx, err := libusb.NewContext() 16 | if err != nil { 17 | log.Fatal("Couldn't create USB context. Ending now.") 18 | } 19 | defer ctx.Close() 20 | devices, err := ctx.DeviceList() 21 | if err != nil { 22 | log.Fatalf("Couldn't get devices") 23 | } 24 | for _, device := range devices { 25 | usbDeviceDescriptor, _ := device.DeviceDescriptor() 26 | handle, err := device.Open() 27 | if err != nil { 28 | log.Fatalf("Error opening device: %s", err) 29 | } 30 | defer handle.Close() 31 | serialNumber, err := handle.StringDescriptorASCII(usbDeviceDescriptor.SerialNumberIndex) 32 | if err != nil { 33 | serialNumber = "N/A" 34 | } 35 | log.Printf("Found S/N: %s", serialNumber) 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /examples/hotplug/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mil-ast/libusb/examples/hotplug 2 | 3 | go 1.21 4 | 5 | require github.com/gotmc/libusb/v2 v2.0.1 6 | 7 | replace github.com/gotmc/libusb/v2 => ../../ 8 | -------------------------------------------------------------------------------- /examples/hotplug/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "time" 7 | 8 | "github.com/gotmc/libusb/v2" 9 | ) 10 | 11 | var ctx *libusb.Context 12 | 13 | func main() { 14 | var err error 15 | ctx, err = libusb.NewContext() 16 | if err != nil { 17 | log.Fatal("Couldn't create USB context. Ending now.") 18 | } 19 | defer ctx.Close() 20 | 21 | // By vID & pID 22 | const ( 23 | vendorID = 0x0930 24 | productID = 0x6545 25 | ) 26 | log.Println("Connect or disconnect any USB device...") 27 | ctx.HotplugRegisterCallbackEvent(vendorID, productID, libusb.HotplugArrived|libusb.HotplugLeft, cb) 28 | time.Sleep(time.Second * 10) 29 | ctx.HotplugDeregisterCallback(vendorID, productID) 30 | 31 | // All devices 32 | log.Println("Connect or disconnect any USB device...") 33 | ctx.HotplugRegisterCallbackEvent(0, 0, libusb.HotplugArrived|libusb.HotplugLeft, cb) 34 | defer ctx.HotplugDeregisterAllCallbacks() 35 | time.Sleep(time.Second * 10) 36 | } 37 | 38 | func cb(vID, pID uint16, eventType libusb.HotPlugEventType) { 39 | fmt.Printf("VendorID: %04x, ProductID: %04x, eventType: %d\r\n", vID, pID, eventType) 40 | } 41 | -------------------------------------------------------------------------------- /examples/key33220/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gotmc/libusb/examples/key33220 2 | 3 | go 1.21 4 | 5 | require github.com/gotmc/libusb/v2 v2.0.0 6 | 7 | replace github.com/gotmc/libusb/v2 => ../../ 8 | -------------------------------------------------------------------------------- /examples/key33220/go.sum: -------------------------------------------------------------------------------- 1 | github.com/gotmc/libusb v1.0.22 h1:+5cE9IHMcJF2R39vVRff0TK5jK4TW0pk2FQNyoxMn7M= 2 | github.com/gotmc/libusb v1.0.22/go.mod h1:fa+kQZl2bW/pna0xryZpEE/ckwD8gqOWjNCewHGp+Aw= 3 | -------------------------------------------------------------------------------- /examples/key33220/key33220: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gotmc/libusb/8b1f3e9b30a39139e64d20ab486dc7a95b1a99ca/examples/key33220/key33220 -------------------------------------------------------------------------------- /examples/key33220/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2025 The libusb developers. All rights reserved. 2 | // Project site: https://github.com/gotmc/libusb 3 | // Use of this source code is governed by a MIT-style license that 4 | // can be found in the LICENSE.txt file for the project. 5 | 6 | package main 7 | 8 | import ( 9 | "bytes" 10 | "encoding/binary" 11 | "fmt" 12 | "log" 13 | "time" 14 | 15 | libusb "github.com/gotmc/libusb/v2" 16 | ) 17 | 18 | const reservedField = 0x00 19 | 20 | const ( 21 | devDepMsgOut msgID = 1 // DEV_DEP_MSG_OUT 22 | ) 23 | 24 | type msgID uint8 25 | 26 | func showVersion() { 27 | version := libusb.Version() 28 | fmt.Printf( 29 | "Using libusb version %d.%d.%d (%d)\n", 30 | version.Major, 31 | version.Minor, 32 | version.Micro, 33 | version.Nano, 34 | ) 35 | } 36 | 37 | func main() { 38 | showVersion() 39 | ctx, err := libusb.NewContext() 40 | if err != nil { 41 | log.Fatal("Couldn't create USB context. Ending now.") 42 | } 43 | defer ctx.Close() 44 | start := time.Now() 45 | devices, _ := ctx.DeviceList() 46 | fmt.Printf("Found %v USB devices (%.4fs elapsed).\n", 47 | len(devices), 48 | time.Since(start).Seconds(), 49 | ) 50 | for _, usbDevice := range devices { 51 | deviceAddress, _ := usbDevice.DeviceAddress() 52 | deviceSpeed, _ := usbDevice.Speed() 53 | busNumber, _ := usbDevice.BusNumber() 54 | usbDeviceDescriptor, _ := usbDevice.DeviceDescriptor() 55 | fmt.Printf("Device address %v is on bus number %v\n=> %v\n", 56 | deviceAddress, 57 | busNumber, 58 | deviceSpeed, 59 | ) 60 | fmt.Printf("=> Vendor: %v \tProduct: %v\n=> Class: %v\n", 61 | usbDeviceDescriptor.VendorID, 62 | usbDeviceDescriptor.ProductID, 63 | usbDeviceDescriptor.DeviceClass, 64 | ) 65 | fmt.Printf("=> USB: %v\tMax Packet 0: %v\tSN Index: %v\n", 66 | usbDeviceDescriptor.USBSpecification, 67 | usbDeviceDescriptor.MaxPacketSize0, 68 | usbDeviceDescriptor.SerialNumberIndex, 69 | ) 70 | } 71 | showInfo(ctx, "Agilent U2751A", 2391, 15640) 72 | // showInfo(ctx, "Agilent 33220A", 2391, 1031) 73 | // showInfo(ctx, "Nike SportWatch", 4524, 21588) 74 | // showInfo(ctx, "Nike FuelBand", 4524, 25957) 75 | 76 | } 77 | 78 | func showInfo(ctx *libusb.Context, name string, vendorID, productID uint16) { 79 | fmt.Printf("Let's open the %s using the Vendor and Product IDs\n", name) 80 | usbDevice, usbDeviceHandle, err := ctx.OpenDeviceWithVendorProduct(vendorID, productID) 81 | usbDeviceDescriptor, _ := usbDevice.DeviceDescriptor() 82 | if err != nil { 83 | fmt.Printf("=> Failed opening the %s: %v\n", name, err) 84 | return 85 | } 86 | defer usbDeviceHandle.Close() 87 | serialnum, _ := usbDeviceHandle.StringDescriptorASCII( 88 | usbDeviceDescriptor.SerialNumberIndex, 89 | ) 90 | manufacturer, _ := usbDeviceHandle.StringDescriptorASCII( 91 | usbDeviceDescriptor.ManufacturerIndex) 92 | product, _ := usbDeviceHandle.StringDescriptorASCII( 93 | usbDeviceDescriptor.ProductIndex) 94 | fmt.Printf("Found %v %v S/N %s using Vendor ID %v and Product ID %v\n", 95 | manufacturer, 96 | product, 97 | serialnum, 98 | vendorID, 99 | productID, 100 | ) 101 | configDescriptor, err := usbDevice.ActiveConfigDescriptor() 102 | if err != nil { 103 | log.Fatalf("Failed getting the active config: %v", err) 104 | } 105 | fmt.Printf("=> Max Power = %d mA\n", 106 | configDescriptor.MaxPowerMilliAmperes) 107 | var singularPlural string 108 | if configDescriptor.NumInterfaces == 1 { 109 | singularPlural = "interface" 110 | } else { 111 | singularPlural = "interfaces" 112 | } 113 | fmt.Printf("=> Found %d %s\n", 114 | configDescriptor.NumInterfaces, singularPlural) 115 | fmt.Printf("=> The first interface has %d alternate settings.\n", 116 | configDescriptor.SupportedInterfaces[0].NumAltSettings) 117 | firstDescriptor := configDescriptor.SupportedInterfaces[0].InterfaceDescriptors[0] 118 | fmt.Printf("=> The first interface descriptor has a length of %d.\n", firstDescriptor.Length) 119 | fmt.Printf( 120 | "=> The first interface descriptor is interface number %d.\n", 121 | firstDescriptor.InterfaceNumber, 122 | ) 123 | fmt.Printf( 124 | "=> The first interface descriptor has %d endpoint(s).\n", 125 | firstDescriptor.NumEndpoints, 126 | ) 127 | fmt.Printf( 128 | " => USB-IF class %d, subclass %d, protocol %d.\n", 129 | firstDescriptor.InterfaceClass, 130 | firstDescriptor.InterfaceSubClass, 131 | firstDescriptor.InterfaceProtocol, 132 | ) 133 | for i, endpoint := range firstDescriptor.EndpointDescriptors { 134 | fmt.Printf( 135 | " => Endpoint index %d on Interface %d has the following properties:\n", 136 | i, firstDescriptor.InterfaceNumber) 137 | fmt.Printf( 138 | " => Address: %d (b%08b)\n", 139 | endpoint.EndpointAddress, 140 | endpoint.EndpointAddress, 141 | ) 142 | fmt.Printf(" => Endpoint #: %d\n", endpoint.Number()) 143 | fmt.Printf(" => Direction: %s (%d)\n", endpoint.Direction(), endpoint.Direction()) 144 | fmt.Printf(" => Attributes: %d (b%08b) \n", endpoint.Attributes, endpoint.Attributes) 145 | fmt.Printf( 146 | " => Transfer Type: %s (%d) \n", 147 | endpoint.TransferType(), 148 | endpoint.TransferType(), 149 | ) 150 | fmt.Printf(" => Max packet size: %d\n", endpoint.MaxPacketSize) 151 | } 152 | 153 | err = usbDeviceHandle.ClaimInterface(0) 154 | if err != nil { 155 | log.Printf("Error claiming interface %s", err) 156 | } 157 | 158 | // Get Capabilities 159 | p := make([]byte, 64) 160 | idx := uint16(0x0000) 161 | n, err := usbDeviceHandle.ControlTransfer(0xA1, 7, 0x0000, idx, p, 0x18, 2000) 162 | if err != nil { 163 | log.Printf("Error sending control transfer: %s", err) 164 | } 165 | log.Printf("Sent %d bytes on control transfer", n) 166 | log.Printf("capabilities = %q", p) 167 | log.Printf("capabilities = %v", p) 168 | log.Printf("cap[14] := %b (%d)", p[14], p[14]) 169 | log.Printf("cap[15] := %b (%d)", p[15], p[15]) 170 | 171 | // Send USBTMC message to Agilent 33220A 172 | bulkOutput := firstDescriptor.EndpointDescriptors[0] 173 | address := bulkOutput.EndpointAddress 174 | fmt.Printf("Set frequency/amplitude on endpoint address %d\n", address) 175 | data := createGotmcMessage("apply:sinusoid 2340, 0.1, 0.0") 176 | transferred, err := usbDeviceHandle.BulkTransfer(address, data, len(data), 5000) 177 | if err != nil { 178 | log.Printf("Error on bulk transfer %s", err) 179 | } 180 | fmt.Printf("Sent %d bytes to 33220A\n", transferred) 181 | err = usbDeviceHandle.ReleaseInterface(0) 182 | if err != nil { 183 | log.Printf("Error releasing interface %s", err) 184 | } 185 | } 186 | 187 | func createDevDepMsgOutBulkOutHeader( 188 | transferSize uint32, eom bool, bTag byte, 189 | ) [12]byte { 190 | // Offset 0-3: See Table 1. 191 | prefix := encodeBulkHeaderPrefix(devDepMsgOut, bTag) 192 | // Offset 4-7: TransferSize 193 | // Per USBTMC Table 3, the TransferSize is the "total number of USBTMC 194 | // message data bytes to be sent in this USB transfer. This does not include 195 | // the number of bytes in this Bulk-OUT Header or alignment bytes. Sent least 196 | // significant byte first, most significant byte last. TransferSize must be > 197 | // 0x00000000." 198 | packedTransferSize := make([]byte, 4) 199 | binary.LittleEndian.PutUint32(packedTransferSize, transferSize) 200 | // Offset 8: bmTransferAttributes 201 | // Per USBTMC Table 3, D0 of bmTransferAttributes: 202 | // 1 - The last USBTMC message data byte in the transfer is the last byte 203 | // of the USBTMC message. 204 | // 0 - The last USBTMC message data byte in the transfer is not the last 205 | // byte of the USBTMC message. 206 | // All other bits of bmTransferAttributes must be 0. 207 | bmTransferAttributes := byte(0x00) 208 | if eom { 209 | bmTransferAttributes = byte(0x01) 210 | } 211 | // Offset 9-11: reservedField. Must be 0x000000. 212 | return [12]byte{ 213 | prefix[0], 214 | prefix[1], 215 | prefix[2], 216 | prefix[3], 217 | packedTransferSize[0], 218 | packedTransferSize[1], 219 | packedTransferSize[2], 220 | packedTransferSize[3], 221 | bmTransferAttributes, 222 | reservedField, 223 | reservedField, 224 | reservedField, 225 | } 226 | } 227 | 228 | // Create the first four bytes of the USBTMC meassage Bulk-OUT Header as shown 229 | // in USBTMC Table 1. The msgID value must match USBTMC Table 2. 230 | func encodeBulkHeaderPrefix(msgID msgID, bTag byte) [4]byte { 231 | return [4]byte{ 232 | byte(msgID), 233 | bTag, 234 | invertbTag(bTag), 235 | reservedField, 236 | } 237 | } 238 | 239 | func invertbTag(bTag byte) byte { 240 | return bTag ^ 0xff 241 | } 242 | 243 | func createGotmcMessage(input string) []byte { 244 | message := []byte(input + "\n") 245 | header := createDevDepMsgOutBulkOutHeader(uint32(len(message)), true, 1) 246 | data := append(header[:], message...) 247 | if moduloFour := len(data) % 4; moduloFour > 0 { 248 | numAlignment := 4 - moduloFour 249 | alignment := bytes.Repeat([]byte{0x00}, numAlignment) 250 | data = append(data, alignment...) 251 | } 252 | return data 253 | } 254 | -------------------------------------------------------------------------------- /examples/keyu2751a/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gotmc/libusb/examples/keyu2751a 2 | 3 | go 1.21 4 | 5 | require github.com/gotmc/libusb/v2 v2.0.0 6 | 7 | replace github.com/gotmc/libusb/v2 => ../../ 8 | -------------------------------------------------------------------------------- /examples/keyu2751a/go.sum: -------------------------------------------------------------------------------- 1 | github.com/gotmc/libusb v1.0.22 h1:+5cE9IHMcJF2R39vVRff0TK5jK4TW0pk2FQNyoxMn7M= 2 | github.com/gotmc/libusb v1.0.22/go.mod h1:fa+kQZl2bW/pna0xryZpEE/ckwD8gqOWjNCewHGp+Aw= 3 | -------------------------------------------------------------------------------- /examples/keyu2751a/keyu2751a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gotmc/libusb/8b1f3e9b30a39139e64d20ab486dc7a95b1a99ca/examples/keyu2751a/keyu2751a -------------------------------------------------------------------------------- /examples/keyu2751a/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2025 The libusb developers. All rights reserved. 2 | // Project site: https://github.com/gotmc/libusb 3 | // Use of this source code is governed by a MIT-style license that 4 | // can be found in the LICENSE.txt file for the project. 5 | 6 | package main 7 | 8 | import ( 9 | "bytes" 10 | "encoding/binary" 11 | "fmt" 12 | "log" 13 | "strings" 14 | "time" 15 | 16 | libusb "github.com/gotmc/libusb/v2" 17 | ) 18 | 19 | const reservedField = 0x00 20 | 21 | const ( 22 | devDepMsgOut msgID = 1 // DEV_DEP_MSG_OUT 23 | reqDevDepMsgOut msgID = 2 // REQUEST_DEV_DEP_MSG_IN 24 | ) 25 | 26 | type msgID uint8 27 | 28 | func showVersion() { 29 | version := libusb.Version() 30 | fmt.Printf( 31 | "Using libusb version %d.%d.%d (%d)\n", 32 | version.Major, 33 | version.Minor, 34 | version.Micro, 35 | version.Nano, 36 | ) 37 | } 38 | 39 | func main() { 40 | showVersion() 41 | ctx, err := libusb.NewContext() 42 | if err != nil { 43 | log.Fatal("Couldn't create USB context. Ending now.") 44 | } 45 | defer ctx.Close() 46 | start := time.Now() 47 | devices, _ := ctx.DeviceList() 48 | fmt.Printf("Found %v USB devices (%.4fs elapsed).\n", 49 | len(devices), 50 | time.Since(start).Seconds(), 51 | ) 52 | for _, usbDevice := range devices { 53 | deviceAddress, _ := usbDevice.DeviceAddress() 54 | deviceSpeed, _ := usbDevice.Speed() 55 | busNumber, _ := usbDevice.BusNumber() 56 | usbDeviceDescriptor, _ := usbDevice.DeviceDescriptor() 57 | fmt.Printf("Device address %v is on bus number %v\n=> %v\n", 58 | deviceAddress, 59 | busNumber, 60 | deviceSpeed, 61 | ) 62 | fmt.Printf("=> Vendor: %v \tProduct: %v\n=> Class: %v\n", 63 | usbDeviceDescriptor.VendorID, 64 | usbDeviceDescriptor.ProductID, 65 | usbDeviceDescriptor.DeviceClass, 66 | ) 67 | fmt.Printf("=> USB: %v\tMax Packet 0: %v\tSN Index: %v\n", 68 | usbDeviceDescriptor.USBSpecification, 69 | usbDeviceDescriptor.MaxPacketSize0, 70 | usbDeviceDescriptor.SerialNumberIndex, 71 | ) 72 | } 73 | showInfo(ctx, "Agilent U2751A", 2391, 15896) 74 | 75 | } 76 | 77 | func showInfo(ctx *libusb.Context, name string, vendorID, productID uint16) { 78 | fmt.Printf( 79 | "Let's open the %s using vendor ID %d and product ID %d\n", 80 | name, 81 | vendorID, 82 | productID, 83 | ) 84 | usbDevice, usbDeviceHandle, err := ctx.OpenDeviceWithVendorProduct(vendorID, productID) 85 | if err != nil { 86 | fmt.Printf("=> Failed opening the %s: %v\n", name, err) 87 | return 88 | } 89 | usbDeviceDescriptor, err := usbDevice.DeviceDescriptor() 90 | if err != nil { 91 | fmt.Printf("=> Failed getting the device descriptor for %s: %v\n", name, err) 92 | return 93 | } 94 | defer usbDeviceHandle.Close() 95 | serialnum, _ := usbDeviceHandle.StringDescriptorASCII( 96 | usbDeviceDescriptor.SerialNumberIndex, 97 | ) 98 | manufacturer, _ := usbDeviceHandle.StringDescriptorASCII( 99 | usbDeviceDescriptor.ManufacturerIndex) 100 | product, _ := usbDeviceHandle.StringDescriptorASCII( 101 | usbDeviceDescriptor.ProductIndex) 102 | fmt.Printf("Manufacturer = %s\n", strings.TrimSpace(manufacturer)) 103 | fmt.Printf("Product = %s\n", strings.TrimSpace(product)) 104 | fmt.Printf("S/N = %s\n", strings.TrimSpace(serialnum)) 105 | configDescriptor, err := usbDevice.ActiveConfigDescriptor() 106 | if err != nil { 107 | log.Fatalf("Failed getting the active config: %v", err) 108 | } 109 | fmt.Printf("=> Max Power = %d mA\n", 110 | configDescriptor.MaxPowerMilliAmperes) 111 | var singularPlural string 112 | if configDescriptor.NumInterfaces == 1 { 113 | singularPlural = "interface" 114 | } else { 115 | singularPlural = "interfaces" 116 | } 117 | fmt.Printf("=> Found %d %s\n", 118 | configDescriptor.NumInterfaces, singularPlural) 119 | fmt.Printf("=> The first interface has %d alternate settings.\n", 120 | configDescriptor.SupportedInterfaces[0].NumAltSettings) 121 | firstDescriptor := configDescriptor.SupportedInterfaces[0].InterfaceDescriptors[0] 122 | fmt.Printf("=> The first interface descriptor has a length of %d.\n", firstDescriptor.Length) 123 | fmt.Printf( 124 | "=> The first interface descriptor is interface number %d.\n", 125 | firstDescriptor.InterfaceNumber, 126 | ) 127 | fmt.Printf( 128 | "=> The first interface descriptor has %d endpoint(s).\n", 129 | firstDescriptor.NumEndpoints, 130 | ) 131 | fmt.Printf( 132 | " => USB-IF class %d, subclass %d, protocol %d.\n", 133 | firstDescriptor.InterfaceClass, 134 | firstDescriptor.InterfaceSubClass, 135 | firstDescriptor.InterfaceProtocol, 136 | ) 137 | for i, endpoint := range firstDescriptor.EndpointDescriptors { 138 | fmt.Printf( 139 | " => Endpoint index %d on Interface %d has the following properties:\n", 140 | i, firstDescriptor.InterfaceNumber) 141 | fmt.Printf( 142 | " => Address: %d (b%08b)\n", 143 | endpoint.EndpointAddress, 144 | endpoint.EndpointAddress, 145 | ) 146 | fmt.Printf(" => Endpoint #: %d\n", endpoint.Number()) 147 | fmt.Printf(" => Direction: %s (%d)\n", endpoint.Direction(), endpoint.Direction()) 148 | fmt.Printf(" => Attributes: %d (b%08b) \n", endpoint.Attributes, endpoint.Attributes) 149 | fmt.Printf( 150 | " => Transfer Type: %s (%d) \n", 151 | endpoint.TransferType(), 152 | endpoint.TransferType(), 153 | ) 154 | fmt.Printf(" => Max packet size: %d\n", endpoint.MaxPacketSize) 155 | } 156 | 157 | // Initiate clear 158 | packets := []struct { 159 | bmRequestType byte 160 | bRequest byte 161 | value uint16 162 | index uint16 163 | data []byte 164 | length int 165 | }{ 166 | {0xC0, 0x0C, 0x0000, 0x047E, make([]byte, 0x01), 0x01}, 167 | {0xC0, 0x0C, 0x0000, 0x047D, make([]byte, 0x06), 0x06}, 168 | {0xC0, 0x0C, 0x0000, 0x0484, make([]byte, 0x05), 0x05}, 169 | {0xC0, 0x0C, 0x0000, 0x0472, make([]byte, 0x0C), 0x0C}, 170 | {0xC0, 0x0C, 0x0000, 0x047A, make([]byte, 0x01), 0x01}, 171 | {0x40, 0x0C, 0x0000, 0x0475, []byte{0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x08, 0x01}, 0x08}, 172 | } 173 | for i, packet := range packets { 174 | _, err = usbDeviceHandle.ControlTransfer( 175 | packet.bmRequestType, 176 | packet.bRequest, 177 | packet.value, 178 | packet.index, 179 | packet.data, 180 | packet.length, 181 | 2000, 182 | ) 183 | if err != nil { 184 | log.Printf("Error sending control transfer #%d: %s", i+1, err) 185 | } 186 | } 187 | } 188 | 189 | func createDevDepMsgOutBulkOutHeader( 190 | transferSize uint32, eom bool, bTag byte, 191 | ) [12]byte { 192 | // Offset 0-3: See Table 1. 193 | prefix := encodeBulkHeaderPrefix(devDepMsgOut, bTag) 194 | // Offset 4-7: TransferSize 195 | // Per USBTMC Table 3, the TransferSize is the "total number of USBTMC 196 | // message data bytes to be sent in this USB transfer. This does not include 197 | // the number of bytes in this Bulk-OUT Header or alignment bytes. Sent least 198 | // significant byte first, most significant byte last. TransferSize must be > 199 | // 0x00000000." 200 | packedTransferSize := make([]byte, 4) 201 | binary.LittleEndian.PutUint32(packedTransferSize, transferSize) 202 | // Offset 8: bmTransferAttributes 203 | // Per USBTMC Table 3, D0 of bmTransferAttributes: 204 | // 1 - The last USBTMC message data byte in the transfer is the last byte 205 | // of the USBTMC message. 206 | // 0 - The last USBTMC message data byte in the transfer is not the last 207 | // byte of the USBTMC message. 208 | // All other bits of bmTransferAttributes must be 0. 209 | bmTransferAttributes := byte(0x00) 210 | if eom { 211 | bmTransferAttributes = byte(0x01) 212 | } 213 | // Offset 9-11: reservedField. Must be 0x000000. 214 | return [12]byte{ 215 | prefix[0], 216 | prefix[1], 217 | prefix[2], 218 | prefix[3], 219 | packedTransferSize[0], 220 | packedTransferSize[1], 221 | packedTransferSize[2], 222 | packedTransferSize[3], 223 | bmTransferAttributes, 224 | reservedField, 225 | reservedField, 226 | reservedField, 227 | } 228 | } 229 | 230 | // Create the first four bytes of the USBTMC meassage Bulk-OUT Header as shown 231 | // in USBTMC Table 1. The msgID value must match USBTMC Table 2. 232 | func encodeBulkHeaderPrefix(msgID msgID, bTag byte) [4]byte { 233 | return [4]byte{ 234 | byte(msgID), 235 | bTag, 236 | invertbTag(bTag), 237 | reservedField, 238 | } 239 | } 240 | 241 | func invertbTag(bTag byte) byte { 242 | return bTag ^ 0xff 243 | } 244 | 245 | func createGotmcMessage(input string) []byte { 246 | message := []byte(input + "\n") 247 | header := createDevDepMsgOutBulkOutHeader(uint32(len(message)), true, 1) 248 | data := append(header[:], message...) 249 | if moduloFour := len(data) % 4; moduloFour > 0 { 250 | numAlignment := 4 - moduloFour 251 | alignment := bytes.Repeat([]byte{0x00}, numAlignment) 252 | data = append(data, alignment...) 253 | } 254 | return data 255 | } 256 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gotmc/libusb/v2 2 | 3 | go 1.21 4 | -------------------------------------------------------------------------------- /handle.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2025 The libusb developers. All rights reserved. 2 | // Project site: https://github.com/gotmc/libusb 3 | // Use of this source code is governed by a MIT-style license that 4 | // can be found in the LICENSE.txt file for the project. 5 | 6 | package libusb 7 | 8 | // #cgo pkg-config: libusb-1.0 9 | // #include 10 | import "C" 11 | import ( 12 | "unsafe" 13 | ) 14 | 15 | // DeviceHandle represents the libusb device handle. 16 | type DeviceHandle struct { 17 | libusbDeviceHandle *C.libusb_device_handle 18 | } 19 | 20 | // StringDescriptor retrieves a descriptor from a device. 21 | func (dh *DeviceHandle) StringDescriptor( 22 | descIndex uint8, 23 | langID uint16, 24 | ) (string, error) { 25 | if dh == nil || dh.libusbDeviceHandle == nil { 26 | return "", ErrorCode(errorInvalidParam) 27 | } 28 | 29 | // Allocate buffer for data 30 | length := 512 31 | cData := make([]C.uchar, length) 32 | 33 | usberr := C.libusb_get_string_descriptor( 34 | dh.libusbDeviceHandle, 35 | C.uint8_t(descIndex), 36 | C.uint16_t(langID), 37 | &cData[0], 38 | C.int(length), 39 | ) 40 | if usberr < 0 { 41 | return "", ErrorCode(usberr) 42 | } 43 | 44 | // Convert to Go string 45 | data := (*C.char)(unsafe.Pointer(&cData[0])) 46 | return C.GoString(data), nil 47 | } 48 | 49 | // StringDescriptorASCII retrieve(s) a string descriptor in C style ASCII. 50 | // Wrapper around libusb_get_string_descriptor(). Uses the first language 51 | // supported by the device. (Source: libusb docs) 52 | func (dh *DeviceHandle) StringDescriptorASCII( 53 | descIndex uint8, 54 | ) (string, error) { 55 | if dh == nil || dh.libusbDeviceHandle == nil { 56 | return "", ErrorCode(errorInvalidParam) 57 | } 58 | 59 | // TODO(mdr): Should the length be a constant? Why did I pick 256 bytes? 60 | length := 256 61 | data := make([]byte, length) 62 | bytesRead, err := C.libusb_get_string_descriptor_ascii( 63 | dh.libusbDeviceHandle, 64 | C.uint8_t(descIndex), 65 | // Unsafe pointer -> https://stackoverflow.com/a/16376039/95592 66 | (*C.uchar)(unsafe.Pointer(&data[0])), 67 | C.int(length), 68 | ) 69 | 70 | // Check both bytesRead and err 71 | if err != nil { 72 | return "", err 73 | } 74 | if bytesRead < 0 { 75 | return "", ErrorCode(bytesRead) 76 | } 77 | return string(data[0:bytesRead]), nil 78 | } 79 | 80 | // Close implements libusb_close to close the device handle. 81 | func (dh *DeviceHandle) Close() error { 82 | if dh == nil || dh.libusbDeviceHandle == nil { 83 | return ErrorCode(errorInvalidParam) 84 | } 85 | C.libusb_close(dh.libusbDeviceHandle) 86 | return nil 87 | } 88 | 89 | // Device implements libusb_get_device to get the underlying device for a 90 | // handle. 91 | // TODO(mdr): Determine if I actually need this function. 92 | // func (dh *DeviceHandle) Device() (*Device, error) { 93 | // } 94 | 95 | // Configuration implements the libusb_get_configuration function to 96 | // determine the bConfigurationValue of the currently active configuration. 97 | func (dh *DeviceHandle) Configuration() (int, error) { 98 | if dh == nil || dh.libusbDeviceHandle == nil { 99 | return 0, ErrorCode(errorInvalidParam) 100 | } 101 | 102 | // Allocate memory for the configuration value 103 | var configuration C.int 104 | err := C.libusb_get_configuration(dh.libusbDeviceHandle, &configuration) 105 | if err != 0 { 106 | return 0, ErrorCode(err) 107 | } 108 | return int(configuration), nil 109 | } 110 | 111 | // SetConfiguration implements libusb_set_configuration to set the active 112 | // configuration for the device. 113 | func (dh *DeviceHandle) SetConfiguration(configuration int) error { 114 | if dh == nil || dh.libusbDeviceHandle == nil { 115 | return ErrorCode(errorInvalidParam) 116 | } 117 | err := C.libusb_set_configuration(dh.libusbDeviceHandle, 118 | C.int(configuration)) 119 | if err != 0 { 120 | return ErrorCode(err) 121 | } 122 | return nil 123 | } 124 | 125 | // ClaimInterface implements libusb_claim_interface to claim an interface on a 126 | // given device handle. You must claim the interface you wish to use before you 127 | // can perform I/O on any of its endpoints. 128 | func (dh *DeviceHandle) ClaimInterface(interfaceNum int) error { 129 | if dh == nil || dh.libusbDeviceHandle == nil { 130 | return ErrorCode(errorInvalidParam) 131 | } 132 | err := C.libusb_claim_interface(dh.libusbDeviceHandle, C.int(interfaceNum)) 133 | if err != 0 { 134 | return ErrorCode(err) 135 | } 136 | return nil 137 | } 138 | 139 | // ReleaseInterface implements libusb_release_interface to release an interface 140 | // previously claimed with libusb_claim_interface() (i.e., ClaimInterface()). 141 | func (dh *DeviceHandle) ReleaseInterface(interfaceNum int) error { 142 | if dh == nil || dh.libusbDeviceHandle == nil { 143 | return ErrorCode(errorInvalidParam) 144 | } 145 | err := C.libusb_release_interface(dh.libusbDeviceHandle, C.int(interfaceNum)) 146 | if err != 0 { 147 | return ErrorCode(err) 148 | } 149 | return nil 150 | } 151 | 152 | // SetInterfaceAltSetting activates an alternate setting for an interface. 153 | func (dh *DeviceHandle) SetInterfaceAltSetting( 154 | interfaceNum int, 155 | alternateSetting int, 156 | ) error { 157 | if dh == nil || dh.libusbDeviceHandle == nil { 158 | return ErrorCode(errorInvalidParam) 159 | } 160 | err := C.libusb_set_interface_alt_setting( 161 | dh.libusbDeviceHandle, 162 | C.int(interfaceNum), 163 | C.int(alternateSetting), 164 | ) 165 | if err != 0 { 166 | return ErrorCode(err) 167 | } 168 | return nil 169 | } 170 | 171 | // FIXME(mdr): libusb_clear_halt takes an endpoint as an unsigned char. Need to 172 | // determine, what I should pass into this function as the endpoint. 173 | // func (dh *DeviceHandle) ClearHalt(endpoint int) error { 174 | // return nil 175 | // } 176 | 177 | // ResetDevice implements libusb_reset_device to perform a USB port reset to 178 | // reinitialize a device. 179 | func (dh *DeviceHandle) ResetDevice() error { 180 | if dh == nil || dh.libusbDeviceHandle == nil { 181 | return ErrorCode(errorInvalidParam) 182 | } 183 | err := C.libusb_reset_device(dh.libusbDeviceHandle) 184 | if err != 0 { 185 | return ErrorCode(err) 186 | } 187 | return nil 188 | } 189 | 190 | // KernelDriverActive implements libusb_kernel_driver_active to determine if a 191 | // kernel driver is active on an interface. 192 | func (dh *DeviceHandle) KernelDriverActive(interfaceNum int) (bool, error) { 193 | if dh == nil || dh.libusbDeviceHandle == nil { 194 | return false, ErrorCode(errorInvalidParam) 195 | } 196 | ret := C.libusb_kernel_driver_active( 197 | dh.libusbDeviceHandle, C.int(interfaceNum)) 198 | if ret == 1 { 199 | return true, nil 200 | } else if ret != 0 { 201 | return false, ErrorCode(ret) 202 | } 203 | return false, nil 204 | } 205 | 206 | // DetachKernelDriver implements libusb_detach_kernel_driver to detach a kernel 207 | // driver from an interface. 208 | func (dh *DeviceHandle) DetachKernelDriver(interfaceNum int) error { 209 | if dh == nil || dh.libusbDeviceHandle == nil { 210 | return ErrorCode(errorInvalidParam) 211 | } 212 | err := C.libusb_detach_kernel_driver( 213 | dh.libusbDeviceHandle, C.int(interfaceNum)) 214 | if err != 0 { 215 | return ErrorCode(err) 216 | } 217 | return nil 218 | } 219 | 220 | // AttachKernelDriver implements libusb_attach_kernel_driver to re-attach an 221 | // interface's kernel driver, which was previously detached using 222 | // libusb_detach_kernel_driver(). 223 | func (dh *DeviceHandle) AttachKernelDriver(interfaceNum int) error { 224 | if dh == nil || dh.libusbDeviceHandle == nil { 225 | return ErrorCode(errorInvalidParam) 226 | } 227 | err := C.libusb_attach_kernel_driver( 228 | dh.libusbDeviceHandle, C.int(interfaceNum)) 229 | if err != 0 { 230 | return ErrorCode(err) 231 | } 232 | return nil 233 | } 234 | 235 | // SetAutoDetachKernelDriver implements libusb_set_auto_detach_kernel_driver to 236 | // enable/disable libusb's automatic kernel driver detachment. 237 | func (dh *DeviceHandle) SetAutoDetachKernelDriver(enable bool) error { 238 | if dh == nil || dh.libusbDeviceHandle == nil { 239 | return ErrorCode(errorInvalidParam) 240 | } 241 | cEnable := C.int(0) 242 | if enable { 243 | cEnable = C.int(1) 244 | } 245 | err := C.libusb_set_auto_detach_kernel_driver(dh.libusbDeviceHandle, cEnable) 246 | if err != 0 { 247 | return ErrorCode(err) 248 | } 249 | return nil 250 | } 251 | -------------------------------------------------------------------------------- /hotplug.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2025 The libusb developers. All rights reserved. 2 | // Project site: https://github.com/gotmc/libusb 3 | // Use of this source code is governed by a MIT-style license that 4 | // can be found in the LICENSE.txt file for the project. 5 | 6 | package libusb 7 | 8 | // #cgo pkg-config: libusb-1.0 9 | // #include 10 | // int libusbHotplugCallback (libusb_context *ctx, libusb_device *device, libusb_hotplug_event event, void *user_data); 11 | // typedef struct libusb_device_descriptor libusb_device_descriptor_struct; 12 | // static int libusb_hotplug_register_callback_wrapper ( 13 | // libusb_context *ctx, 14 | // int events, int flags, 15 | // int vendor_id, int product_id, int dev_class, 16 | // libusb_hotplug_callback_fn cb_fn, void *user_data, 17 | // libusb_hotplug_callback_handle *callback_handle) 18 | // { 19 | // return libusb_hotplug_register_callback(ctx, events, flags, vendor_id, product_id, dev_class, cb_fn, user_data, callback_handle); 20 | // } 21 | import "C" 22 | import ( 23 | "fmt" 24 | "log" 25 | "sync" 26 | "unsafe" 27 | ) 28 | 29 | // HotPlugEventType ... 30 | type HotPlugEventType uint8 31 | 32 | // HotPlugCbFunc callback 33 | type HotPlugCbFunc func(vID, pID uint16, eventType HotPlugEventType) 34 | 35 | // HotPlug Event Types 36 | const ( 37 | HotplugUndefined HotPlugEventType = iota 38 | HotplugArrived 39 | HotplugLeft 40 | ) 41 | 42 | // HotPlugEvent callback message 43 | type HotPlugEvent struct { 44 | VendorID uint16 45 | ProductID uint16 46 | Event HotPlugEventType 47 | } 48 | 49 | type hotplugCallback struct { 50 | handler *C.libusb_hotplug_callback_handle 51 | fn HotPlugCbFunc 52 | } 53 | 54 | // HotplugCallbackStorage ... 55 | type HotplugCallbackStorage struct { 56 | callbackMap map[uint32]hotplugCallback 57 | done chan struct{} 58 | mu sync.RWMutex // Protects the callbackMap from concurrent access 59 | } 60 | 61 | var hotplugCallbackStorage HotplugCallbackStorage 62 | 63 | func (ctx *Context) newHotPlugHandler() { 64 | hotplugCallbackStorage.callbackMap = make(map[uint32]hotplugCallback) 65 | hotplugCallbackStorage.done = make(chan struct{}) 66 | 67 | go hotplugCallbackStorage.handleEvents(ctx.libusbContext) 68 | } 69 | 70 | func (s *HotplugCallbackStorage) isEmpty() bool { 71 | s.mu.RLock() 72 | defer s.mu.RUnlock() 73 | return s.callbackMap == nil 74 | } 75 | 76 | // HotplugRegisterCallbackEvent ... 77 | func (ctx *Context) HotplugRegisterCallbackEvent(vendorID, productID uint16, eventType HotPlugEventType, cb HotPlugCbFunc) error { 78 | if hotplugCallbackStorage.isEmpty() { 79 | ctx.newHotPlugHandler() 80 | } 81 | 82 | var event C.int 83 | switch eventType { 84 | case HotplugArrived: 85 | event = C.LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED 86 | case HotplugLeft: 87 | event = C.LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT 88 | default: 89 | event = C.LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | C.LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT 90 | } 91 | 92 | var vID C.int = C.LIBUSB_HOTPLUG_MATCH_ANY 93 | var pID C.int = C.LIBUSB_HOTPLUG_MATCH_ANY 94 | 95 | if vendorID != 0 { 96 | vID = C.int(vendorID) 97 | } 98 | if productID != 0 { 99 | pID = C.int(productID) 100 | } 101 | 102 | var cbHandle C.libusb_hotplug_callback_handle 103 | 104 | rc := C.libusb_hotplug_register_callback_wrapper( 105 | ctx.libusbContext, 106 | event, 107 | C.LIBUSB_HOTPLUG_NO_FLAGS, 108 | vID, 109 | pID, 110 | C.LIBUSB_HOTPLUG_MATCH_ANY, 111 | C.libusb_hotplug_callback_fn(unsafe.Pointer(C.libusbHotplugCallback)), 112 | nil, 113 | &cbHandle, 114 | ) 115 | if rc != C.LIBUSB_SUCCESS { 116 | return fmt.Errorf("libusb_hotplug_register_callback error: %s", ErrorCode(rc)) 117 | } 118 | 119 | hotplugCallbackStorage.mu.Lock() 120 | hotplugCallbackStorage.callbackMap[vidPidToUint32(vendorID, productID)] = hotplugCallback{ 121 | handler: &cbHandle, 122 | fn: cb, 123 | } 124 | hotplugCallbackStorage.mu.Unlock() 125 | 126 | return nil 127 | } 128 | 129 | // HotplugDeregisterCallback ... 130 | func (ctx *Context) HotplugDeregisterCallback(vendorID, productID uint16) error { 131 | if hotplugCallbackStorage.isEmpty() { 132 | return nil 133 | } 134 | 135 | key := vidPidToUint32(vendorID, productID) 136 | 137 | hotplugCallbackStorage.mu.RLock() 138 | cb, ok := hotplugCallbackStorage.callbackMap[key] 139 | hotplugCallbackStorage.mu.RUnlock() 140 | 141 | if !ok { 142 | return nil 143 | } 144 | 145 | C.libusb_hotplug_deregister_callback(ctx.libusbContext, *cb.handler) 146 | 147 | hotplugCallbackStorage.mu.Lock() 148 | delete(hotplugCallbackStorage.callbackMap, key) 149 | mapEmpty := len(hotplugCallbackStorage.callbackMap) == 0 150 | hotplugCallbackStorage.mu.Unlock() 151 | 152 | if mapEmpty { 153 | ctx.hotplugHandleEventsCompleteAll() 154 | } 155 | return nil 156 | } 157 | 158 | // HotplugDeregisterAllCallbacks ... 159 | func (ctx *Context) HotplugDeregisterAllCallbacks() error { 160 | hotplugCallbackStorage.mu.RLock() 161 | mapExists := hotplugCallbackStorage.callbackMap != nil 162 | 163 | if mapExists { 164 | // Make a copy of the handlers to avoid holding the lock during C function calls 165 | handlers := make([]*C.libusb_hotplug_callback_handle, 0, len(hotplugCallbackStorage.callbackMap)) 166 | for _, cb := range hotplugCallbackStorage.callbackMap { 167 | handlers = append(handlers, cb.handler) 168 | } 169 | hotplugCallbackStorage.mu.RUnlock() 170 | 171 | // Deregister callbacks without holding the lock 172 | for _, handler := range handlers { 173 | C.libusb_hotplug_deregister_callback(ctx.libusbContext, *handler) 174 | } 175 | } else { 176 | hotplugCallbackStorage.mu.RUnlock() 177 | } 178 | 179 | ctx.hotplugHandleEventsCompleteAll() 180 | 181 | return nil 182 | } 183 | 184 | func (ctx *Context) hotplugHandleEventsCompleteAll() { 185 | if hotplugCallbackStorage.isEmpty() { 186 | return 187 | } 188 | 189 | // Signal the event handler to stop 190 | hotplugCallbackStorage.done <- struct{}{} 191 | 192 | // Clear the callbackMap and close the channel 193 | hotplugCallbackStorage.mu.Lock() 194 | hotplugCallbackStorage.callbackMap = nil 195 | hotplugCallbackStorage.mu.Unlock() 196 | 197 | close(hotplugCallbackStorage.done) 198 | } 199 | 200 | func (storage *HotplugCallbackStorage) handleEvents(libCtx *C.libusb_context) { 201 | for { 202 | select { 203 | case <-storage.done: 204 | return 205 | default: 206 | } 207 | if errno := C.libusb_handle_events_completed(libCtx, nil); errno < 0 { 208 | log.Printf("handle_events error: %s", ErrorCode(errno)) 209 | } 210 | } 211 | } 212 | 213 | //export libusbHotplugCallback 214 | func libusbHotplugCallback(ctx *C.libusb_context, dev *C.libusb_device, event C.libusb_hotplug_event, p unsafe.Pointer) C.int { 215 | var desc C.libusb_device_descriptor_struct 216 | rc := C.libusb_get_device_descriptor(dev, &desc) 217 | if rc != C.LIBUSB_SUCCESS { 218 | return rc 219 | } 220 | 221 | var vendorID = uint16(desc.idVendor) 222 | var productID = uint16(desc.idProduct) 223 | 224 | var e HotPlugEventType 225 | switch event { 226 | case C.LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED: 227 | e = HotplugArrived 228 | case C.LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT: 229 | e = HotplugLeft 230 | default: 231 | e = HotplugUndefined 232 | } 233 | 234 | // Read callback map with a read lock 235 | hotplugCallbackStorage.mu.RLock() 236 | // Get device-specific callback 237 | cb, ok := hotplugCallbackStorage.callbackMap[vidPidToUint32(vendorID, productID)] 238 | var deviceCallback HotPlugCbFunc 239 | if ok { 240 | deviceCallback = cb.fn 241 | } 242 | 243 | // Get the callback for all devices 244 | cb, ok = hotplugCallbackStorage.callbackMap[0] 245 | var allCallback HotPlugCbFunc 246 | if ok { 247 | allCallback = cb.fn 248 | } 249 | hotplugCallbackStorage.mu.RUnlock() 250 | 251 | // Call callbacks outside the lock 252 | if deviceCallback != nil { 253 | deviceCallback(vendorID, productID, e) 254 | } 255 | 256 | if allCallback != nil { 257 | allCallback(vendorID, productID, e) 258 | } 259 | 260 | return C.LIBUSB_SUCCESS 261 | } 262 | 263 | func vidPidToUint32(vID, pID uint16) uint32 { 264 | return (uint32(vID) << 16) | (uint32(pID)) 265 | } 266 | -------------------------------------------------------------------------------- /interfaces.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2025 The libusb developers. All rights reserved. 2 | // Project site: https://github.com/gotmc/libusb 3 | // Use of this source code is governed by a MIT-style license that 4 | // can be found in the LICENSE.txt file for the project. 5 | 6 | package libusb 7 | 8 | // #cgo pkg-config: libusb-1.0 9 | // #include 10 | import "C" 11 | 12 | // SupportedInterface models an supported USB interface and its associated 13 | // interface descriptors. 14 | type SupportedInterface struct { 15 | InterfaceDescriptors 16 | NumAltSettings int 17 | } 18 | 19 | // SupportedInterfaces contains an array of the supported USB interfaces for a 20 | // given USB device. 21 | type SupportedInterfaces []*SupportedInterface 22 | 23 | // USB Interface Class Codes 24 | const ( 25 | // USB Interface Class Codes - useful for finding interfaces by class 26 | InterfaceClassAudio uint8 = 0x01 27 | InterfaceClassComm uint8 = 0x02 28 | InterfaceClassHID uint8 = 0x03 29 | InterfaceClassPhysical uint8 = 0x05 30 | InterfaceClassImage uint8 = 0x06 31 | InterfaceClassPrinter uint8 = 0x07 32 | InterfaceClassMassStorage uint8 = 0x08 33 | InterfaceClassHub uint8 = 0x09 34 | InterfaceClassData uint8 = 0x0A 35 | InterfaceClassSmartCard uint8 = 0x0B 36 | InterfaceClassContentSecurity uint8 = 0x0D 37 | InterfaceClassVideo uint8 = 0x0E 38 | InterfaceClassPersonalHealthcare uint8 = 0x0F 39 | InterfaceClassAudioVideo uint8 = 0x10 40 | InterfaceClassWireless uint8 = 0xE0 41 | InterfaceClassApplication uint8 = 0xFE 42 | InterfaceClassVendorSpec uint8 = 0xFF 43 | ) 44 | 45 | // GetAllInterfacesByClass searches through all supported interfaces and their interface 46 | // descriptors to find all interfaces matching the given class code. 47 | // This is especially useful for printer devices (InterfaceClassPrinter) and other specialized devices. 48 | func (si SupportedInterfaces) GetAllInterfacesByClass(class uint8) InterfaceDescriptors { 49 | var matchingInterfaces InterfaceDescriptors 50 | for _, supportedIface := range si { 51 | for _, ifaceDesc := range supportedIface.InterfaceDescriptors { 52 | if ifaceDesc.InterfaceClass == class { 53 | matchingInterfaces = append(matchingInterfaces, ifaceDesc) 54 | } 55 | } 56 | } 57 | return matchingInterfaces 58 | } 59 | 60 | // InterfaceDescriptor "provides information about a function or feature that a 61 | // device implements." (Source: *USB Complete* 5th edition by Jan Axelson) 62 | type InterfaceDescriptor struct { 63 | Length int 64 | DescriptorType descriptorType 65 | InterfaceNumber int 66 | AlternateSetting int 67 | NumEndpoints int 68 | InterfaceClass uint8 69 | InterfaceSubClass uint8 70 | InterfaceProtocol uint8 71 | InterfaceIndex int 72 | EndpointDescriptors 73 | } 74 | 75 | // InterfaceDescriptors contains a slice of pointers to the available interface 76 | // descriptors. 77 | type InterfaceDescriptors []*InterfaceDescriptor 78 | 79 | // GetInterfacesByClass returns all interface descriptors that match the given USB class code. 80 | // This is particularly useful for devices that report bDeviceClass as LIBUSB_CLASS_PER_INTERFACE 81 | // (0), where individual interfaces have their own class codes. 82 | func (ifaces InterfaceDescriptors) GetInterfacesByClass(class uint8) InterfaceDescriptors { 83 | var matchingInterfaces InterfaceDescriptors 84 | for _, iface := range ifaces { 85 | if iface.InterfaceClass == class { 86 | matchingInterfaces = append(matchingInterfaces, iface) 87 | } 88 | } 89 | return matchingInterfaces 90 | } 91 | -------------------------------------------------------------------------------- /miscellaneous.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2025 The libusb developers. All rights reserved. 2 | // Project site: https://github.com/gotmc/libusb 3 | // Use of this source code is governed by a MIT-style license that 4 | // can be found in the LICENSE.txt file for the project. 5 | 6 | package libusb 7 | 8 | // #cgo pkg-config: libusb-1.0 9 | // #include 10 | // static inline const char* libusb_strerror_wrapper (int code) { 11 | // return libusb_strerror(code); 12 | // } 13 | import "C" 14 | import ( 15 | "fmt" 16 | "math" 17 | ) 18 | 19 | // ErrorCode is the type for the libusb_error C enum. 20 | type ErrorCode int 21 | 22 | // Error implements the Go error interface for ErrorCode. 23 | func (err ErrorCode) Error() string { 24 | return fmt.Sprintf("%v: %v", 25 | ErrorName(err), 26 | StrError(err), 27 | ) 28 | } 29 | 30 | // ErrorName implements the libusb_error_name function. 31 | func ErrorName(err ErrorCode) string { 32 | // Convert directly to C.int to avoid potential type mismatches across platforms 33 | return C.GoString(C.libusb_error_name(C.int(int(err)))) 34 | } 35 | 36 | // StrError implements the libusb_strerror function. 37 | func StrError(err ErrorCode) string { 38 | // Convert directly to C.int to avoid potential type mismatches across platforms 39 | return C.GoString(C.libusb_strerror_wrapper(C.int(int(err)))) 40 | } 41 | 42 | // SetLocale sets the locale for libusb errors. 43 | func SetLocale(locale string) ErrorCode { 44 | // Explicitly convert to ensure type compatibility 45 | return ErrorCode(int(C.libusb_setlocale(C.CString(locale)))) 46 | } 47 | 48 | // CPUtoLE16 converts "a 16-bit value from host-endian to little-endian format. 49 | // On little endian systems, this function does nothing. On big endian systems, 50 | // the bytes are swapped. 51 | func CPUtoLE16(value int) int { 52 | return int(C.libusb_cpu_to_le16(C.uint16_t(value))) 53 | } 54 | 55 | // HasCapability checks "at runtime if the loaded library has a given 56 | // capability. This call should be performed after libusb_init(), to ensure 57 | // the backend has updated its capability set." (Source: libusb docs) 58 | func HasCapability(capability int) bool { 59 | return C.libusb_has_capability(C.uint32_t(capability)) != 0 60 | } 61 | 62 | const ( 63 | success ErrorCode = C.LIBUSB_SUCCESS 64 | errorIo ErrorCode = C.LIBUSB_ERROR_IO 65 | errorInvalidParam ErrorCode = C.LIBUSB_ERROR_INVALID_PARAM 66 | errorAccess ErrorCode = C.LIBUSB_ERROR_ACCESS 67 | errorNoDevice ErrorCode = C.LIBUSB_ERROR_NO_DEVICE 68 | errorNotFound ErrorCode = C.LIBUSB_ERROR_NOT_FOUND 69 | errorBusy ErrorCode = C.LIBUSB_ERROR_BUSY 70 | errorTimeout ErrorCode = C.LIBUSB_ERROR_TIMEOUT 71 | errorOverflow ErrorCode = C.LIBUSB_ERROR_OVERFLOW 72 | errorPipe ErrorCode = C.LIBUSB_ERROR_PIPE 73 | errorInterrupted ErrorCode = C.LIBUSB_ERROR_INTERRUPTED 74 | errorNoMem ErrorCode = C.LIBUSB_ERROR_NO_MEM 75 | errorNotSupported ErrorCode = C.LIBUSB_ERROR_NOT_SUPPORTED 76 | errorOther ErrorCode = C.LIBUSB_ERROR_OTHER 77 | 78 | errorTransferError ErrorCode = C.LIBUSB_TRANSFER_ERROR 79 | errorTransferTimedOut ErrorCode = C.LIBUSB_TRANSFER_TIMED_OUT 80 | errorTransferCanceled ErrorCode = C.LIBUSB_TRANSFER_CANCELLED 81 | errorTransferStall ErrorCode = C.LIBUSB_TRANSFER_STALL 82 | errorTransferNoDevice ErrorCode = C.LIBUSB_TRANSFER_NO_DEVICE 83 | errorTransferOverflow ErrorCode = C.LIBUSB_TRANSFER_OVERFLOW 84 | ) 85 | 86 | func bcdToDecimal(bcdValue uint16) float64 { 87 | bcdPowersByPosition := []string{"hundreths", "tenths", "ones", "tens"} 88 | 89 | bcdMap := make(map[string]uint16) 90 | for i, power := range bcdPowersByPosition { 91 | bcdMap[power] = bcdValue & (0xf << uint(4*i)) / uint16(math.Pow(16, float64(i))) 92 | } 93 | return 10*float64(bcdMap["tens"]) + float64(bcdMap["ones"]) + 94 | 0.1*float64(bcdMap["tenths"]) + 0.01*float64(bcdMap["hundreths"]) 95 | } 96 | -------------------------------------------------------------------------------- /miscellaneous_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2025 The libusb developers. All rights reserved. 2 | // Project site: https://github.com/gotmc/libusb 3 | // Use of this source code is governed by a MIT-style license that 4 | // can be found in the LICENSE.txt file for the project. 5 | 6 | package libusb 7 | 8 | import ( 9 | "fmt" 10 | "testing" 11 | ) 12 | 13 | func ExampleErrorCode_Error() { 14 | SetLocale("en") 15 | fmt.Println(success.Error()) 16 | fmt.Println(errorIo.Error()) 17 | fmt.Println(errorInvalidParam.Error()) 18 | fmt.Println(errorAccess.Error()) 19 | fmt.Println(errorNoDevice.Error()) 20 | fmt.Println(errorNotFound.Error()) 21 | fmt.Println(errorBusy.Error()) 22 | fmt.Println(errorTimeout.Error()) 23 | fmt.Println(errorOverflow.Error()) 24 | fmt.Println(errorPipe.Error()) 25 | fmt.Println(errorInterrupted.Error()) 26 | fmt.Println(errorNoMem.Error()) 27 | fmt.Println(errorNotSupported.Error()) 28 | fmt.Println(errorOther.Error()) 29 | 30 | // Output: 31 | // LIBUSB_SUCCESS / LIBUSB_TRANSFER_COMPLETED: Success 32 | // LIBUSB_ERROR_IO: Input/Output Error 33 | // LIBUSB_ERROR_INVALID_PARAM: Invalid parameter 34 | // LIBUSB_ERROR_ACCESS: Access denied (insufficient permissions) 35 | // LIBUSB_ERROR_NO_DEVICE: No such device (it may have been disconnected) 36 | // LIBUSB_ERROR_NOT_FOUND: Entity not found 37 | // LIBUSB_ERROR_BUSY: Resource busy 38 | // LIBUSB_ERROR_TIMEOUT: Operation timed out 39 | // LIBUSB_ERROR_OVERFLOW: Overflow 40 | // LIBUSB_ERROR_PIPE: Pipe error 41 | // LIBUSB_ERROR_INTERRUPTED: System call interrupted (perhaps due to signal) 42 | // LIBUSB_ERROR_NO_MEM: Insufficient memory 43 | // LIBUSB_ERROR_NOT_SUPPORTED: Operation not supported or unimplemented on this platform 44 | // LIBUSB_ERROR_OTHER: Other error 45 | } 46 | 47 | func TestBcdToDecimal(t *testing.T) { 48 | testCases := []struct { 49 | bcdValue uint16 50 | want float64 51 | }{ 52 | {0x0110, 1.1}, 53 | {0x0200, 2.0}, 54 | {0x0210, 2.1}, 55 | {0x0300, 3.0}, 56 | {0x0310, 3.1}, 57 | } 58 | for _, tc := range testCases { 59 | if got := bcdToDecimal(tc.bcdValue); got != tc.want { 60 | t.Errorf( 61 | "Error converting BCD to decimal\n\tgot %v; want %v", 62 | got, 63 | tc.want, 64 | ) 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /setupdata.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2025 The libusb developers. All rights reserved. 2 | // Project site: https://github.com/gotmc/libusb 3 | // Use of this source code is governed by a MIT-style license that 4 | // can be found in the LICENSE.txt file for the project. 5 | 6 | package libusb 7 | 8 | // #cgo pkg-config: libusb-1.0 9 | // #include 10 | import "C" 11 | 12 | // TransferDirection represents the direction of a control transfer. 13 | type TransferDirection byte 14 | 15 | // Constants to set the transfer direction. 16 | const ( 17 | HostToDevice TransferDirection = 0x00 18 | DeviceToHost TransferDirection = 0x80 19 | ) 20 | 21 | var transferDirections = map[TransferDirection]string{ 22 | HostToDevice: "Host-to-device", 23 | DeviceToHost: "Device-to-host", 24 | } 25 | 26 | // String implements the Stringer interface for TransferDirection. 27 | func (dir TransferDirection) String() string { 28 | return transferDirections[dir] 29 | } 30 | 31 | // RequestType represents the type of control request. 32 | type RequestType byte 33 | 34 | // Constants representing the libusb request types. 35 | const ( 36 | Standard RequestType = C.LIBUSB_REQUEST_TYPE_STANDARD 37 | Class RequestType = C.LIBUSB_REQUEST_TYPE_CLASS 38 | Vendor RequestType = C.LIBUSB_REQUEST_TYPE_VENDOR 39 | Reserved RequestType = C.LIBUSB_REQUEST_TYPE_RESERVED 40 | ) 41 | 42 | var requestTypes = map[RequestType]string{ 43 | Standard: "Standard", 44 | Class: "Class", 45 | Vendor: "Vendor", 46 | Reserved: "Reserved", 47 | } 48 | 49 | func (rt RequestType) String() string { 50 | return requestTypes[rt] 51 | } 52 | 53 | // RequestRecipient represents the recipient of a control request. 54 | type RequestRecipient byte 55 | 56 | // Constants representing the libusb recipient types. 57 | const ( 58 | DeviceRecipient RequestRecipient = C.LIBUSB_RECIPIENT_DEVICE 59 | InterfaceRecipient RequestRecipient = C.LIBUSB_RECIPIENT_INTERFACE 60 | EndpointRecipient RequestRecipient = C.LIBUSB_RECIPIENT_ENDPOINT 61 | OtherRecipient RequestRecipient = C.LIBUSB_RECIPIENT_OTHER 62 | ) 63 | 64 | var requestRecipients = map[RequestRecipient]string{ 65 | DeviceRecipient: "Device", 66 | InterfaceRecipient: "Interface", 67 | EndpointRecipient: "Endpoint", 68 | OtherRecipient: "Other", 69 | } 70 | 71 | func (r RequestRecipient) String() string { 72 | return requestRecipients[r] 73 | } 74 | 75 | // BitmapRequestType creates a bmRequestType byte from individual components. 76 | // Bits 0:4 determine recipient, see libusb_request_recipient. 77 | // Bits 5:6 determine type, see libusb_request_type. 78 | // Bit 7 determines data transfer direction, see libusb_endpoint_direction. 79 | func BitmapRequestType(dir TransferDirection, reqType RequestType, recipient RequestRecipient) byte { 80 | return byte(dir) | byte(reqType) | byte(recipient) 81 | } 82 | -------------------------------------------------------------------------------- /speeds.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2025 The libusb developers. All rights reserved. 2 | // Project site: https://github.com/gotmc/libusb 3 | // Use of this source code is governed by a MIT-style license that 4 | // can be found in the LICENSE.txt file for the project. 5 | 6 | package libusb 7 | 8 | // #cgo pkg-config: libusb-1.0 9 | // #include 10 | import "C" 11 | 12 | // SpeedType provides the USB speed type. 13 | type SpeedType int 14 | 15 | const ( 16 | speedUnknown SpeedType = C.LIBUSB_SPEED_UNKNOWN 17 | speedLow SpeedType = C.LIBUSB_SPEED_LOW 18 | speedFull SpeedType = C.LIBUSB_SPEED_FULL 19 | speedHigh SpeedType = C.LIBUSB_SPEED_HIGH 20 | speedSuper SpeedType = C.LIBUSB_SPEED_SUPER 21 | ) 22 | 23 | var speedCodes = map[SpeedType]string{ 24 | speedUnknown: "The OS doesn't report or know the device speed.", 25 | speedLow: "The device is operating at low speed (1.5MBit/s)", 26 | speedFull: "The device is operating at full speed (12MBit/s)", 27 | speedHigh: "The device is operating at high speed (480MBit/s)", 28 | speedSuper: "The device is operating at super speed (5000MBit/s)", 29 | } 30 | 31 | func (speed SpeedType) String() string { 32 | return speedCodes[speed] 33 | } 34 | 35 | type supportedSpeed int 36 | 37 | const ( 38 | lowSpeedOperation supportedSpeed = C.LIBUSB_LOW_SPEED_OPERATION 39 | fullSpeedOperation supportedSpeed = C.LIBUSB_FULL_SPEED_OPERATION 40 | highSpeedOperation supportedSpeed = C.LIBUSB_HIGH_SPEED_OPERATION 41 | superSpeedOperation supportedSpeed = C.LIBUSB_SUPER_SPEED_OPERATION 42 | ) 43 | 44 | var supportedSpeeds = map[supportedSpeed]string{ 45 | lowSpeedOperation: "Low speed operation supported (1.5MBit/s).", 46 | fullSpeedOperation: "Full speed operation supported (12MBit/s).", 47 | highSpeedOperation: "High speed operation supported (480MBit/s).", 48 | superSpeedOperation: "Superspeed operation supported (5000MBit/s).", 49 | } 50 | 51 | func (speed supportedSpeed) String() string { 52 | return supportedSpeeds[speed] 53 | } 54 | -------------------------------------------------------------------------------- /syncio.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2025 The libusb developers. All rights reserved. 2 | // Project site: https://github.com/gotmc/libusb 3 | // Use of this source code is governed by a MIT-style license that 4 | // can be found in the LICENSE.txt file for the project. 5 | 6 | package libusb 7 | 8 | // #cgo pkg-config: libusb-1.0 9 | // #include 10 | import "C" 11 | import "unsafe" 12 | 13 | // BulkTransfer implements libusb_bulk_transfer to perform a USB bulk transfer. 14 | func (dh *DeviceHandle) BulkTransfer( 15 | endpoint endpointAddress, 16 | data []byte, 17 | length int, 18 | timeout int, 19 | ) (int, error) { 20 | var transferred C.int 21 | err := C.libusb_bulk_transfer( 22 | dh.libusbDeviceHandle, 23 | C.uchar(endpoint), 24 | (*C.uchar)(unsafe.Pointer(&data[0])), 25 | C.int(length), 26 | &transferred, 27 | C.uint(timeout), 28 | ) 29 | if err != 0 { 30 | return 0, ErrorCode(err) 31 | } 32 | return int(transferred), nil 33 | } 34 | 35 | // BulkTransferOut is a helper method that performs a USB bulk output transfer. 36 | func (dh *DeviceHandle) BulkTransferOut( 37 | endpoint endpointAddress, 38 | data []byte, 39 | timeout int, 40 | ) (int, error) { 41 | return dh.BulkTransfer( 42 | endpoint, 43 | data, 44 | len(data), 45 | timeout, 46 | ) 47 | } 48 | 49 | // BulkTransferIn is a helper method that performs a USB bulk input transfer. 50 | func (dh *DeviceHandle) BulkTransferIn( 51 | endpoint endpointAddress, 52 | maxReceiveBytes int, 53 | timeout int, 54 | ) ([]byte, int, error) { 55 | data := make([]byte, maxReceiveBytes) 56 | transferred, err := dh.BulkTransfer( 57 | endpoint, 58 | data, 59 | maxReceiveBytes, 60 | timeout, 61 | ) 62 | if err != nil { 63 | return nil, 0, err 64 | } 65 | return data, int(transferred), nil 66 | } 67 | 68 | // ControlTransfer sends a transfer using a control endpoint for the given 69 | // device handle. 70 | func (dh *DeviceHandle) ControlTransfer( 71 | requestType byte, 72 | request byte, 73 | value uint16, 74 | index uint16, 75 | data []byte, 76 | length int, 77 | timeout int, 78 | ) (int, error) { 79 | if dh == nil || dh.libusbDeviceHandle == nil { 80 | return 0, ErrorCode(errorInvalidParam) 81 | } 82 | 83 | ret := C.libusb_control_transfer( 84 | dh.libusbDeviceHandle, 85 | C.uint8_t(requestType), 86 | C.uint8_t(request), 87 | C.uint16_t(value), 88 | C.uint16_t(index), 89 | (*C.uchar)(unsafe.Pointer(&data[0])), 90 | C.uint16_t(length), 91 | C.uint(timeout), 92 | ) 93 | if ret < 0 { 94 | return 0, ErrorCode(ret) 95 | } 96 | return int(ret), nil 97 | } 98 | 99 | // ControlTransferWithTypes is a more type-safe version of ControlTransfer that accepts 100 | // TransferDirection, RequestType, and RequestRecipient components to build the bmRequestType. 101 | // This allows for more readable and maintainable code when using constant values. 102 | func (dh *DeviceHandle) ControlTransferWithTypes( 103 | direction TransferDirection, 104 | reqType RequestType, 105 | recipient RequestRecipient, 106 | request byte, 107 | value uint16, 108 | index uint16, 109 | data []byte, 110 | length int, 111 | timeout int, 112 | ) (int, error) { 113 | requestType := BitmapRequestType(direction, reqType, recipient) 114 | return dh.ControlTransfer( 115 | requestType, 116 | request, 117 | value, 118 | index, 119 | data, 120 | length, 121 | timeout, 122 | ) 123 | } 124 | 125 | // ControlOut is a helper method for control OUT transfers (host to device) 126 | func (dh *DeviceHandle) ControlOut( 127 | reqType RequestType, 128 | recipient RequestRecipient, 129 | request byte, 130 | value uint16, 131 | index uint16, 132 | data []byte, 133 | timeout int, 134 | ) (int, error) { 135 | return dh.ControlTransferWithTypes( 136 | HostToDevice, 137 | reqType, 138 | recipient, 139 | request, 140 | value, 141 | index, 142 | data, 143 | len(data), 144 | timeout, 145 | ) 146 | } 147 | 148 | // ControlIn is a helper method for control IN transfers (device to host) 149 | func (dh *DeviceHandle) ControlIn( 150 | reqType RequestType, 151 | recipient RequestRecipient, 152 | request byte, 153 | value uint16, 154 | index uint16, 155 | data []byte, 156 | maxReceiveLength int, 157 | timeout int, 158 | ) (int, error) { 159 | return dh.ControlTransferWithTypes( 160 | DeviceToHost, 161 | reqType, 162 | recipient, 163 | request, 164 | value, 165 | index, 166 | data, 167 | maxReceiveLength, 168 | timeout, 169 | ) 170 | } 171 | 172 | // InterruptTransfer performs a USB interrupt transfer. 173 | func (dh *DeviceHandle) InterruptTransfer( 174 | endpoint endpointAddress, 175 | data []byte, 176 | length int, 177 | timeout int, 178 | ) (int, error) { 179 | var transferred C.int 180 | err := C.libusb_interrupt_transfer( 181 | dh.libusbDeviceHandle, 182 | C.uchar(endpoint), 183 | (*C.uchar)(unsafe.Pointer(&data[0])), 184 | C.int(length), 185 | &transferred, 186 | C.uint(timeout), 187 | ) 188 | if err != 0 { 189 | return 0, ErrorCode(err) 190 | } 191 | return int(transferred), nil 192 | } 193 | -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2025 The libusb developers. All rights reserved. 2 | // Project site: https://github.com/gotmc/libusb 3 | // Use of this source code is governed by a MIT-style license that 4 | // can be found in the LICENSE.txt file for the project. 5 | 6 | package libusb 7 | 8 | // #cgo pkg-config: libusb-1.0 9 | // #include 10 | import "C" 11 | 12 | // The VersionType struct represents the libusb version. 13 | type VersionType struct { 14 | Major uint16 15 | Minor uint16 16 | Micro uint16 17 | Nano uint16 18 | ReleaseCandidate string 19 | Describe string 20 | } 21 | 22 | // Version gets the libusb version and returns a Version struct. 23 | func Version() VersionType { 24 | cVersion := *C.libusb_get_version() 25 | version := VersionType{ 26 | Major: uint16(cVersion.major), 27 | Minor: uint16(cVersion.minor), 28 | Micro: uint16(cVersion.micro), 29 | Nano: uint16(cVersion.nano), 30 | ReleaseCandidate: C.GoString(cVersion.rc), 31 | Describe: C.GoString(cVersion.describe), 32 | } 33 | return version 34 | } 35 | -------------------------------------------------------------------------------- /version_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2025 The libusb developers. All rights reserved. 2 | // Project site: https://github.com/gotmc/libusb 3 | // Use of this source code is governed by a MIT-style license that 4 | // can be found in the LICENSE.txt file for the project. 5 | 6 | package libusb 7 | 8 | import "testing" 9 | 10 | func TestVersion(t *testing.T) { 11 | const major = 1 12 | const minor = 0 13 | const minMicro = 17 14 | releaseCandidate := "" 15 | version := Version() 16 | if version.Major != major { 17 | t.Errorf( 18 | "Major version == %d, want %d", 19 | version.Major, major) 20 | } 21 | if version.Minor != minor { 22 | t.Errorf( 23 | "Minor version == %d, want %d", 24 | version.Minor, minor) 25 | } 26 | if version.Micro < minMicro { 27 | t.Errorf( 28 | "Micro version == %d, need at least %d", 29 | version.Micro, minMicro) 30 | } 31 | if version.ReleaseCandidate != releaseCandidate { 32 | t.Errorf( 33 | "ReleaseCandidate == %v, want %v", 34 | version.ReleaseCandidate, releaseCandidate) 35 | } 36 | } 37 | --------------------------------------------------------------------------------