Cleanup
This commit is contained in:
@@ -24,54 +24,17 @@ func main() {
|
|||||||
ctx.Debug(*debug)
|
ctx.Debug(*debug)
|
||||||
|
|
||||||
// ListDevices is used to find the devices to open.
|
// ListDevices is used to find the devices to open.
|
||||||
devs, err := ctx.ListDevices(func(bus, addr int, desc *usb.Descriptor) bool {
|
devs, err := ctx.ListDevices(func(desc *usb.Descriptor) bool {
|
||||||
// After inspecting the descriptor, return true or false depending on whether
|
|
||||||
// the device is "interesting" or not. Any descriptor for which true is returned
|
|
||||||
// generates a DeviceInfo which is retuned in a slice.
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
// All DeviceInfo returned from ListDevices must be closed.
|
|
||||||
defer func() {
|
|
||||||
for _, d := range devs {
|
|
||||||
d.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// ListDevices can occaionally fail, so be sure to check its return value.
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("list: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, dev := range devs {
|
|
||||||
// The descriptor (which contains useful information) can always be obtained again
|
|
||||||
// from a DeviceInfo.
|
|
||||||
desc, err := dev.Descriptor()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("desc: %s", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// The DeviceInfo contains the bus number and bus address of the device.
|
|
||||||
bus := dev.BusNumber()
|
|
||||||
addr := dev.Address()
|
|
||||||
|
|
||||||
// The usbid package can be used to print out human readable information.
|
// The usbid package can be used to print out human readable information.
|
||||||
fmt.Printf("%03d:%03d %s\n", bus, addr, usbid.Describe(desc))
|
fmt.Printf("%03d.%03d %s\n", desc.Bus, desc.Address, usbid.Describe(desc))
|
||||||
fmt.Printf(" Protocol: %s\n", usbid.Classify(desc))
|
fmt.Printf(" Protocol: %s\n", usbid.Classify(desc))
|
||||||
|
|
||||||
// The configurations can be examined from the DeviceInfo, though they can only
|
// The configurations can be examined from the Descriptor, though they can only
|
||||||
// be set once the device is opened. All configuration references must be closed,
|
// be set once the device is opened. All configuration references must be closed,
|
||||||
// to free up the memory in libusb.
|
// to free up the memory in libusb.
|
||||||
cfgs, err := dev.Configurations()
|
for _, cfg := range desc.Configs {
|
||||||
if err != nil {
|
// This loop just uses more of the built-in and usbid pretty printing to list
|
||||||
log.Printf(" - configs: %s", err)
|
// the USB devices.
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// This loop just uses more of the built-in and usbid pretty printing to list
|
|
||||||
// the USB devices.
|
|
||||||
for _, cfg := range cfgs {
|
|
||||||
fmt.Printf(" %s:\n", cfg)
|
fmt.Printf(" %s:\n", cfg)
|
||||||
for _, alt := range cfg.Interfaces {
|
for _, alt := range cfg.Interfaces {
|
||||||
fmt.Printf(" --------------\n")
|
fmt.Printf(" --------------\n")
|
||||||
@@ -86,7 +49,27 @@ func main() {
|
|||||||
fmt.Printf(" --------------\n")
|
fmt.Printf(" --------------\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// To actually interact with a device, DevInfo.Open() returns a device handle
|
// After inspecting the descriptor, return true or false depending on whether
|
||||||
// which can be used to do more I/O with the device and its endpoints.
|
// the device is "interesting" or not. Any descriptor for which true is returned
|
||||||
|
// opens a Device which is retuned in a slice (and must be subsequently closed).
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
// All Devices returned from ListDevices must be closed.
|
||||||
|
defer func() {
|
||||||
|
for _, d := range devs {
|
||||||
|
d.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// ListDevices can occaionally fail, so be sure to check its return value.
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("list: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dev := range devs {
|
||||||
|
// Once the device has been selected from ListDevices, it is opened
|
||||||
|
// and can be interacted with.
|
||||||
|
_ = dev
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -44,27 +44,27 @@ type InterfaceInfo struct {
|
|||||||
IfClass uint8
|
IfClass uint8
|
||||||
IfSubClass uint8
|
IfSubClass uint8
|
||||||
IfProtocol uint8
|
IfProtocol uint8
|
||||||
Endpoints []*EndpointInfo
|
Endpoints []EndpointInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i InterfaceInfo) String() string {
|
func (i InterfaceInfo) String() string {
|
||||||
return fmt.Sprintf("Interface %02x (config %02x)", i.Number, i.Alternate)
|
return fmt.Sprintf("Interface %02x (config %02x)", i.Number, i.Alternate)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type ConfigInfo struct {
|
||||||
Type DescriptorType
|
Type DescriptorType
|
||||||
Config uint8
|
Config uint8
|
||||||
Attributes uint8
|
Attributes uint8
|
||||||
MaxPower uint8
|
MaxPower uint8
|
||||||
Interfaces [][]*InterfaceInfo
|
Interfaces [][]InterfaceInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Config) String() string {
|
func (c ConfigInfo) String() string {
|
||||||
return fmt.Sprintf("Config %02x", c.Config)
|
return fmt.Sprintf("Config %02x", c.Config)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newConfig(cfg *C.struct_libusb_config_descriptor) *Config {
|
func newConfig(cfg *C.struct_libusb_config_descriptor) ConfigInfo {
|
||||||
c := &Config{
|
c := ConfigInfo{
|
||||||
Type: DescriptorType(cfg.bDescriptorType),
|
Type: DescriptorType(cfg.bDescriptorType),
|
||||||
Config: uint8(cfg.bConfigurationValue),
|
Config: uint8(cfg.bConfigurationValue),
|
||||||
Attributes: uint8(cfg.bmAttributes),
|
Attributes: uint8(cfg.bmAttributes),
|
||||||
@@ -77,7 +77,7 @@ func newConfig(cfg *C.struct_libusb_config_descriptor) *Config {
|
|||||||
Len: int(cfg.bNumInterfaces),
|
Len: int(cfg.bNumInterfaces),
|
||||||
Cap: int(cfg.bNumInterfaces),
|
Cap: int(cfg.bNumInterfaces),
|
||||||
}
|
}
|
||||||
c.Interfaces = make([][]*InterfaceInfo, 0, len(ifaces))
|
c.Interfaces = make([][]InterfaceInfo, 0, len(ifaces))
|
||||||
for _, iface := range ifaces {
|
for _, iface := range ifaces {
|
||||||
var alts []C.struct_libusb_interface_descriptor
|
var alts []C.struct_libusb_interface_descriptor
|
||||||
*(*reflect.SliceHeader)(unsafe.Pointer(&alts)) = reflect.SliceHeader{
|
*(*reflect.SliceHeader)(unsafe.Pointer(&alts)) = reflect.SliceHeader{
|
||||||
@@ -85,9 +85,9 @@ func newConfig(cfg *C.struct_libusb_config_descriptor) *Config {
|
|||||||
Len: int(iface.num_altsetting),
|
Len: int(iface.num_altsetting),
|
||||||
Cap: int(iface.num_altsetting),
|
Cap: int(iface.num_altsetting),
|
||||||
}
|
}
|
||||||
descs := make([]*InterfaceInfo, 0, len(alts))
|
descs := make([]InterfaceInfo, 0, len(alts))
|
||||||
for _, alt := range alts {
|
for _, alt := range alts {
|
||||||
i := &InterfaceInfo{
|
i := InterfaceInfo{
|
||||||
Type: DescriptorType(alt.bDescriptorType),
|
Type: DescriptorType(alt.bDescriptorType),
|
||||||
Number: uint8(alt.bInterfaceNumber),
|
Number: uint8(alt.bInterfaceNumber),
|
||||||
Alternate: uint8(alt.bAlternateSetting),
|
Alternate: uint8(alt.bAlternateSetting),
|
||||||
@@ -101,9 +101,9 @@ func newConfig(cfg *C.struct_libusb_config_descriptor) *Config {
|
|||||||
Len: int(alt.bNumEndpoints),
|
Len: int(alt.bNumEndpoints),
|
||||||
Cap: int(alt.bNumEndpoints),
|
Cap: int(alt.bNumEndpoints),
|
||||||
}
|
}
|
||||||
i.Endpoints = make([]*EndpointInfo, 0, len(ends))
|
i.Endpoints = make([]EndpointInfo, 0, len(ends))
|
||||||
for _, end := range ends {
|
for _, end := range ends {
|
||||||
i.Endpoints = append(i.Endpoints, &EndpointInfo{
|
i.Endpoints = append(i.Endpoints, EndpointInfo{
|
||||||
Type: DescriptorType(end.bDescriptorType),
|
Type: DescriptorType(end.bDescriptorType),
|
||||||
Address: uint8(end.bEndpointAddress),
|
Address: uint8(end.bEndpointAddress),
|
||||||
Attributes: uint8(end.bmAttributes),
|
Attributes: uint8(end.bmAttributes),
|
||||||
|
150
usb/constants.go
Normal file
150
usb/constants.go
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
package usb
|
||||||
|
|
||||||
|
// #cgo LDFLAGS: -lusb-1.0
|
||||||
|
// #include <libusb-1.0/libusb.h>
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
type Class uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
CLASS_PER_INTERFACE Class = C.LIBUSB_CLASS_PER_INTERFACE
|
||||||
|
CLASS_AUDIO Class = C.LIBUSB_CLASS_AUDIO
|
||||||
|
CLASS_COMM Class = C.LIBUSB_CLASS_COMM
|
||||||
|
CLASS_HID Class = C.LIBUSB_CLASS_HID
|
||||||
|
CLASS_PRINTER Class = C.LIBUSB_CLASS_PRINTER
|
||||||
|
CLASS_PTP Class = C.LIBUSB_CLASS_PTP
|
||||||
|
CLASS_MASS_STORAGE Class = C.LIBUSB_CLASS_MASS_STORAGE
|
||||||
|
CLASS_HUB Class = C.LIBUSB_CLASS_HUB
|
||||||
|
CLASS_DATA Class = C.LIBUSB_CLASS_DATA
|
||||||
|
CLASS_WIRELESS Class = C.LIBUSB_CLASS_WIRELESS
|
||||||
|
CLASS_APPLICATION Class = C.LIBUSB_CLASS_APPLICATION
|
||||||
|
CLASS_VENDOR_SPEC Class = C.LIBUSB_CLASS_VENDOR_SPEC
|
||||||
|
)
|
||||||
|
|
||||||
|
var classDescription = map[Class]string{
|
||||||
|
CLASS_PER_INTERFACE: "per-interface",
|
||||||
|
CLASS_AUDIO: "audio",
|
||||||
|
CLASS_COMM: "communications",
|
||||||
|
CLASS_HID: "human interface device",
|
||||||
|
CLASS_PRINTER: "printer dclass",
|
||||||
|
CLASS_PTP: "picture transfer protocol",
|
||||||
|
CLASS_MASS_STORAGE: "mass storage",
|
||||||
|
CLASS_HUB: "hub",
|
||||||
|
CLASS_DATA: "data",
|
||||||
|
CLASS_WIRELESS: "wireless",
|
||||||
|
CLASS_APPLICATION: "application",
|
||||||
|
CLASS_VENDOR_SPEC: "vendor-specific",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Class) String() string {
|
||||||
|
return classDescription[c]
|
||||||
|
}
|
||||||
|
|
||||||
|
type DescriptorType uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
DT_DEVICE DescriptorType = C.LIBUSB_DT_DEVICE
|
||||||
|
DT_CONFIG DescriptorType = C.LIBUSB_DT_CONFIG
|
||||||
|
DT_STRING DescriptorType = C.LIBUSB_DT_STRING
|
||||||
|
DT_INTERFACE DescriptorType = C.LIBUSB_DT_INTERFACE
|
||||||
|
DT_ENDPOINT DescriptorType = C.LIBUSB_DT_ENDPOINT
|
||||||
|
DT_HID DescriptorType = C.LIBUSB_DT_HID
|
||||||
|
DT_REPORT DescriptorType = C.LIBUSB_DT_REPORT
|
||||||
|
DT_PHYSICAL DescriptorType = C.LIBUSB_DT_PHYSICAL
|
||||||
|
DT_HUB DescriptorType = C.LIBUSB_DT_HUB
|
||||||
|
)
|
||||||
|
|
||||||
|
var descriptorTypeDescription = map[DescriptorType]string{
|
||||||
|
DT_DEVICE: "device",
|
||||||
|
DT_CONFIG: "configuration",
|
||||||
|
DT_STRING: "string",
|
||||||
|
DT_INTERFACE: "interface",
|
||||||
|
DT_ENDPOINT: "endpoint",
|
||||||
|
DT_HID: "HID",
|
||||||
|
DT_REPORT: "HID report",
|
||||||
|
DT_PHYSICAL: "physical",
|
||||||
|
DT_HUB: "hub",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dt DescriptorType) String() string {
|
||||||
|
return descriptorTypeDescription[dt]
|
||||||
|
}
|
||||||
|
|
||||||
|
type EndpointDirection uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
ENDPOINT_NUM_MASK = 0x03
|
||||||
|
ENDPOINT_DIR_IN EndpointDirection = C.LIBUSB_ENDPOINT_IN
|
||||||
|
ENDPOINT_DIR_OUT EndpointDirection = C.LIBUSB_ENDPOINT_OUT
|
||||||
|
ENDPOINT_DIR_MASK EndpointDirection = 0x80
|
||||||
|
)
|
||||||
|
|
||||||
|
var endpointDirectionDescription = map[EndpointDirection]string{
|
||||||
|
ENDPOINT_DIR_IN: "IN",
|
||||||
|
ENDPOINT_DIR_OUT: "OUT",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ed EndpointDirection) String() string {
|
||||||
|
return endpointDirectionDescription[ed]
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransferType uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
TRANSFER_TYPE_CONTROL TransferType = C.LIBUSB_TRANSFER_TYPE_CONTROL
|
||||||
|
TRANSFER_TYPE_ISOCHRONOUS TransferType = C.LIBUSB_TRANSFER_TYPE_ISOCHRONOUS
|
||||||
|
TRANSFER_TYPE_BULK TransferType = C.LIBUSB_TRANSFER_TYPE_BULK
|
||||||
|
TRANSFER_TYPE_INTERRUPT TransferType = C.LIBUSB_TRANSFER_TYPE_INTERRUPT
|
||||||
|
TRANSFER_TYPE_MASK TransferType = 0x03
|
||||||
|
)
|
||||||
|
|
||||||
|
var transferTypeDescription = map[TransferType]string{
|
||||||
|
TRANSFER_TYPE_CONTROL: "control",
|
||||||
|
TRANSFER_TYPE_ISOCHRONOUS: "isochronous",
|
||||||
|
TRANSFER_TYPE_BULK: "bulk",
|
||||||
|
TRANSFER_TYPE_INTERRUPT: "interrupt",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tt TransferType) String() string {
|
||||||
|
return transferTypeDescription[tt]
|
||||||
|
}
|
||||||
|
|
||||||
|
type IsoSyncType uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
ISO_SYNC_TYPE_NONE IsoSyncType = C.LIBUSB_ISO_SYNC_TYPE_NONE << 2
|
||||||
|
ISO_SYNC_TYPE_ASYNC IsoSyncType = C.LIBUSB_ISO_SYNC_TYPE_ASYNC << 2
|
||||||
|
ISO_SYNC_TYPE_ADAPTIVE IsoSyncType = C.LIBUSB_ISO_SYNC_TYPE_ADAPTIVE << 2
|
||||||
|
ISO_SYNC_TYPE_SYNC IsoSyncType = C.LIBUSB_ISO_SYNC_TYPE_SYNC << 2
|
||||||
|
ISO_SYNC_TYPE_MASK IsoSyncType = 0x0C
|
||||||
|
)
|
||||||
|
|
||||||
|
var isoSyncTypeDescription = map[IsoSyncType]string{
|
||||||
|
ISO_SYNC_TYPE_NONE: "unsynchronized",
|
||||||
|
ISO_SYNC_TYPE_ASYNC: "asynchronous",
|
||||||
|
ISO_SYNC_TYPE_ADAPTIVE: "adaptive",
|
||||||
|
ISO_SYNC_TYPE_SYNC: "synchronous",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ist IsoSyncType) String() string {
|
||||||
|
return isoSyncTypeDescription[ist]
|
||||||
|
}
|
||||||
|
|
||||||
|
type IsoUsageType uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
ISO_USAGE_TYPE_DATA IsoUsageType = C.LIBUSB_ISO_USAGE_TYPE_DATA << 4
|
||||||
|
ISO_USAGE_TYPE_FEEDBACK IsoUsageType = C.LIBUSB_ISO_USAGE_TYPE_FEEDBACK << 4
|
||||||
|
ISO_USAGE_TYPE_IMPLICIT IsoUsageType = C.LIBUSB_ISO_USAGE_TYPE_IMPLICIT << 4
|
||||||
|
ISO_USAGE_TYPE_MASK IsoUsageType = 0x30
|
||||||
|
)
|
||||||
|
|
||||||
|
var isoUsageTypeDescription = map[IsoUsageType]string{
|
||||||
|
ISO_USAGE_TYPE_DATA: "data",
|
||||||
|
ISO_USAGE_TYPE_FEEDBACK: "feedback",
|
||||||
|
ISO_USAGE_TYPE_IMPLICIT: "implicit data",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iut IsoUsageType) String() string {
|
||||||
|
return isoUsageTypeDescription[iut]
|
||||||
|
}
|
@@ -5,16 +5,25 @@ package usb
|
|||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
type Descriptor struct {
|
type Descriptor struct {
|
||||||
desc *C.struct_libusb_device_descriptor
|
// Bus information
|
||||||
|
Bus uint8 // The bus on which the device was detected
|
||||||
|
Address uint8 // The address of the device on the bus
|
||||||
|
|
||||||
Type DescriptorType // The type of this descriptor
|
// Version information
|
||||||
Spec BCD // USB Specification Release Number
|
Spec BCD // USB Specification Release Number
|
||||||
Class uint8 // The class of this device
|
Device BCD // The device version
|
||||||
SubClass uint8 // The sub-class (within the class) of this device
|
|
||||||
Protocol uint8 // The protocol (within the sub-class) of this device
|
// Product information
|
||||||
Vendor ID // The 8-bit Vendor identifer
|
Vendor ID // The Vendor identifer
|
||||||
Product ID // The 8-bit Product identifier
|
Product ID // The Product identifier
|
||||||
Device BCD // The device version
|
|
||||||
|
// Protocol information
|
||||||
|
Class uint8 // The class of this device
|
||||||
|
SubClass uint8 // The sub-class (within the class) of this device
|
||||||
|
Protocol uint8 // The protocol (within the sub-class) of this device
|
||||||
|
|
||||||
|
// Configuration information
|
||||||
|
Configs []ConfigInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDescriptor(dev *C.libusb_device) (*Descriptor, error) {
|
func newDescriptor(dev *C.libusb_device) (*Descriptor, error) {
|
||||||
@@ -22,196 +31,28 @@ func newDescriptor(dev *C.libusb_device) (*Descriptor, error) {
|
|||||||
if errno := C.libusb_get_device_descriptor(dev, &desc); errno < 0 {
|
if errno := C.libusb_get_device_descriptor(dev, &desc); errno < 0 {
|
||||||
return nil, usbError(errno)
|
return nil, usbError(errno)
|
||||||
}
|
}
|
||||||
return &Descriptor{
|
|
||||||
desc: &desc,
|
|
||||||
Type: DescriptorType(desc.bDescriptorType),
|
|
||||||
Spec: BCD(desc.bcdUSB),
|
|
||||||
Class: uint8(desc.bDeviceClass),
|
|
||||||
SubClass: uint8(desc.bDeviceSubClass),
|
|
||||||
Protocol: uint8(desc.bDeviceProtocol),
|
|
||||||
Vendor: ID(desc.idVendor),
|
|
||||||
Product: ID(desc.idProduct),
|
|
||||||
Device: BCD(desc.bcdDevice),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configurations returns a list of configurations configured on this
|
// Enumerate configurations
|
||||||
// device. Each config must be Closed.
|
var cfgs []ConfigInfo
|
||||||
func (d *DeviceInfo) Configurations() ([]*Config, error) {
|
|
||||||
var desc C.struct_libusb_device_descriptor
|
|
||||||
if errno := C.libusb_get_device_descriptor(d.dev, &desc); errno < 0 {
|
|
||||||
return nil, usbError(errno)
|
|
||||||
}
|
|
||||||
|
|
||||||
var cfgs []*Config
|
|
||||||
for i := 0; i < int(desc.bNumConfigurations); i++ {
|
for i := 0; i < int(desc.bNumConfigurations); i++ {
|
||||||
var cfg *C.struct_libusb_config_descriptor
|
var cfg *C.struct_libusb_config_descriptor
|
||||||
if errno := C.libusb_get_config_descriptor(d.dev, C.uint8_t(i), &cfg); errno < 0 {
|
if errno := C.libusb_get_config_descriptor(dev, C.uint8_t(i), &cfg); errno < 0 {
|
||||||
return nil, usbError(errno)
|
return nil, usbError(errno)
|
||||||
}
|
}
|
||||||
cfgs = append(cfgs, newConfig(cfg))
|
cfgs = append(cfgs, newConfig(cfg))
|
||||||
C.libusb_free_config_descriptor(cfg)
|
C.libusb_free_config_descriptor(cfg)
|
||||||
}
|
}
|
||||||
return cfgs, nil
|
|
||||||
}
|
return &Descriptor{
|
||||||
|
Bus: uint8(C.libusb_get_bus_number(dev)),
|
||||||
/*
|
Address: uint8(C.libusb_get_device_address(dev)),
|
||||||
func (d Descriptor) str(idx int) (string, error) {
|
Spec: BCD(desc.bcdUSB),
|
||||||
str := [64]byte{}
|
Device: BCD(desc.bcdDevice),
|
||||||
n := C.libusb_get_string_descriptor_ascii(
|
Vendor: ID(desc.idVendor),
|
||||||
d.dev.handle,
|
Product: ID(desc.idProduct),
|
||||||
C.uint8_t(idx),
|
Class: uint8(desc.bDeviceClass),
|
||||||
(*C.uchar)(unsafe.Pointer(&str)),
|
SubClass: uint8(desc.bDeviceSubClass),
|
||||||
64,
|
Protocol: uint8(desc.bDeviceProtocol),
|
||||||
)
|
Configs: cfgs,
|
||||||
if n < 0 {
|
}, nil
|
||||||
return "", usbError(n)
|
|
||||||
}
|
|
||||||
return string(str[:n]), nil
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
type Class int
|
|
||||||
|
|
||||||
const (
|
|
||||||
CLASS_PER_INTERFACE Class = C.LIBUSB_CLASS_PER_INTERFACE
|
|
||||||
CLASS_AUDIO Class = C.LIBUSB_CLASS_AUDIO
|
|
||||||
CLASS_COMM Class = C.LIBUSB_CLASS_COMM
|
|
||||||
CLASS_HID Class = C.LIBUSB_CLASS_HID
|
|
||||||
CLASS_PRINTER Class = C.LIBUSB_CLASS_PRINTER
|
|
||||||
CLASS_PTP Class = C.LIBUSB_CLASS_PTP
|
|
||||||
CLASS_MASS_STORAGE Class = C.LIBUSB_CLASS_MASS_STORAGE
|
|
||||||
CLASS_HUB Class = C.LIBUSB_CLASS_HUB
|
|
||||||
CLASS_DATA Class = C.LIBUSB_CLASS_DATA
|
|
||||||
CLASS_WIRELESS Class = C.LIBUSB_CLASS_WIRELESS
|
|
||||||
CLASS_APPLICATION Class = C.LIBUSB_CLASS_APPLICATION
|
|
||||||
CLASS_VENDOR_SPEC Class = C.LIBUSB_CLASS_VENDOR_SPEC
|
|
||||||
)
|
|
||||||
|
|
||||||
var classDescription = map[Class]string{
|
|
||||||
CLASS_PER_INTERFACE: "per-interface",
|
|
||||||
CLASS_AUDIO: "audio",
|
|
||||||
CLASS_COMM: "communications",
|
|
||||||
CLASS_HID: "human interface device",
|
|
||||||
CLASS_PRINTER: "printer dclass",
|
|
||||||
CLASS_PTP: "picture transfer protocol",
|
|
||||||
CLASS_MASS_STORAGE: "mass storage",
|
|
||||||
CLASS_HUB: "hub",
|
|
||||||
CLASS_DATA: "data",
|
|
||||||
CLASS_WIRELESS: "wireless",
|
|
||||||
CLASS_APPLICATION: "application",
|
|
||||||
CLASS_VENDOR_SPEC: "vendor-specific",
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Class) String() string {
|
|
||||||
return classDescription[c]
|
|
||||||
}
|
|
||||||
|
|
||||||
type DescriptorType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
DT_DEVICE DescriptorType = C.LIBUSB_DT_DEVICE
|
|
||||||
DT_CONFIG DescriptorType = C.LIBUSB_DT_CONFIG
|
|
||||||
DT_STRING DescriptorType = C.LIBUSB_DT_STRING
|
|
||||||
DT_INTERFACE DescriptorType = C.LIBUSB_DT_INTERFACE
|
|
||||||
DT_ENDPOINT DescriptorType = C.LIBUSB_DT_ENDPOINT
|
|
||||||
DT_HID DescriptorType = C.LIBUSB_DT_HID
|
|
||||||
DT_REPORT DescriptorType = C.LIBUSB_DT_REPORT
|
|
||||||
DT_PHYSICAL DescriptorType = C.LIBUSB_DT_PHYSICAL
|
|
||||||
DT_HUB DescriptorType = C.LIBUSB_DT_HUB
|
|
||||||
)
|
|
||||||
|
|
||||||
var descriptorTypeDescription = map[DescriptorType]string{
|
|
||||||
DT_DEVICE: "device",
|
|
||||||
DT_CONFIG: "configuration",
|
|
||||||
DT_STRING: "string",
|
|
||||||
DT_INTERFACE: "interface",
|
|
||||||
DT_ENDPOINT: "endpoint",
|
|
||||||
DT_HID: "HID",
|
|
||||||
DT_REPORT: "HID report",
|
|
||||||
DT_PHYSICAL: "physical",
|
|
||||||
DT_HUB: "hub",
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dt DescriptorType) String() string {
|
|
||||||
return descriptorTypeDescription[dt]
|
|
||||||
}
|
|
||||||
|
|
||||||
type EndpointDirection int
|
|
||||||
|
|
||||||
const (
|
|
||||||
ENDPOINT_NUM_MASK = 0x03
|
|
||||||
ENDPOINT_DIR_IN EndpointDirection = C.LIBUSB_ENDPOINT_IN
|
|
||||||
ENDPOINT_DIR_OUT EndpointDirection = C.LIBUSB_ENDPOINT_OUT
|
|
||||||
ENDPOINT_DIR_MASK EndpointDirection = 0x80
|
|
||||||
)
|
|
||||||
|
|
||||||
var endpointDirectionDescription = map[EndpointDirection]string{
|
|
||||||
ENDPOINT_DIR_IN: "IN",
|
|
||||||
ENDPOINT_DIR_OUT: "OUT",
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ed EndpointDirection) String() string {
|
|
||||||
return endpointDirectionDescription[ed]
|
|
||||||
}
|
|
||||||
|
|
||||||
type TransferType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
TRANSFER_TYPE_CONTROL TransferType = C.LIBUSB_TRANSFER_TYPE_CONTROL
|
|
||||||
TRANSFER_TYPE_ISOCHRONOUS TransferType = C.LIBUSB_TRANSFER_TYPE_ISOCHRONOUS
|
|
||||||
TRANSFER_TYPE_BULK TransferType = C.LIBUSB_TRANSFER_TYPE_BULK
|
|
||||||
TRANSFER_TYPE_INTERRUPT TransferType = C.LIBUSB_TRANSFER_TYPE_INTERRUPT
|
|
||||||
TRANSFER_TYPE_MASK TransferType = 0x03
|
|
||||||
)
|
|
||||||
|
|
||||||
var transferTypeDescription = map[TransferType]string{
|
|
||||||
TRANSFER_TYPE_CONTROL: "control",
|
|
||||||
TRANSFER_TYPE_ISOCHRONOUS: "isochronous",
|
|
||||||
TRANSFER_TYPE_BULK: "bulk",
|
|
||||||
TRANSFER_TYPE_INTERRUPT: "interrupt",
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tt TransferType) String() string {
|
|
||||||
return transferTypeDescription[tt]
|
|
||||||
}
|
|
||||||
|
|
||||||
type IsoSyncType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
ISO_SYNC_TYPE_NONE IsoSyncType = C.LIBUSB_ISO_SYNC_TYPE_NONE << 2
|
|
||||||
ISO_SYNC_TYPE_ASYNC IsoSyncType = C.LIBUSB_ISO_SYNC_TYPE_ASYNC << 2
|
|
||||||
ISO_SYNC_TYPE_ADAPTIVE IsoSyncType = C.LIBUSB_ISO_SYNC_TYPE_ADAPTIVE << 2
|
|
||||||
ISO_SYNC_TYPE_SYNC IsoSyncType = C.LIBUSB_ISO_SYNC_TYPE_SYNC << 2
|
|
||||||
ISO_SYNC_TYPE_MASK IsoSyncType = 0x0C
|
|
||||||
)
|
|
||||||
|
|
||||||
var isoSyncTypeDescription = map[IsoSyncType]string{
|
|
||||||
ISO_SYNC_TYPE_NONE: "unsynchronized",
|
|
||||||
ISO_SYNC_TYPE_ASYNC: "asynchronous",
|
|
||||||
ISO_SYNC_TYPE_ADAPTIVE: "adaptive",
|
|
||||||
ISO_SYNC_TYPE_SYNC: "synchronous",
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ist IsoSyncType) String() string {
|
|
||||||
return isoSyncTypeDescription[ist]
|
|
||||||
}
|
|
||||||
|
|
||||||
type IsoUsageType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
ISO_USAGE_TYPE_DATA IsoUsageType = C.LIBUSB_ISO_USAGE_TYPE_DATA << 4
|
|
||||||
ISO_USAGE_TYPE_FEEDBACK IsoUsageType = C.LIBUSB_ISO_USAGE_TYPE_FEEDBACK << 4
|
|
||||||
ISO_USAGE_TYPE_IMPLICIT IsoUsageType = C.LIBUSB_ISO_USAGE_TYPE_IMPLICIT << 4
|
|
||||||
ISO_USAGE_TYPE_MASK IsoUsageType = 0x30
|
|
||||||
)
|
|
||||||
|
|
||||||
var isoUsageTypeDescription = map[IsoUsageType]string{
|
|
||||||
ISO_USAGE_TYPE_DATA: "data",
|
|
||||||
ISO_USAGE_TYPE_FEEDBACK: "feedback",
|
|
||||||
ISO_USAGE_TYPE_IMPLICIT: "implicit data",
|
|
||||||
}
|
|
||||||
|
|
||||||
func (iut IsoUsageType) String() string {
|
|
||||||
return isoUsageTypeDescription[iut]
|
|
||||||
}
|
}
|
||||||
|
@@ -5,80 +5,35 @@ package usb
|
|||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DeviceInfo struct {
|
var DefaultControlTimeout = 5 * time.Second
|
||||||
dev *C.libusb_device
|
|
||||||
}
|
|
||||||
|
|
||||||
func newDeviceInfo(dev *C.libusb_device) *DeviceInfo {
|
|
||||||
d := &DeviceInfo{
|
|
||||||
dev: dev,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reference this device
|
|
||||||
C.libusb_ref_device(dev)
|
|
||||||
|
|
||||||
// I still can't get this to be called
|
|
||||||
runtime.SetFinalizer(d, (*DeviceInfo).Close)
|
|
||||||
|
|
||||||
//log.Printf("deviceInfo %p initialized", d.dev)
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the assigned Bus Number for this USB device.
|
|
||||||
func (d *DeviceInfo) BusNumber() byte {
|
|
||||||
return byte(C.libusb_get_bus_number(d.dev))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the assigned Bus Address for this USB device.
|
|
||||||
func (d *DeviceInfo) Address() byte {
|
|
||||||
return byte(C.libusb_get_device_address(d.dev))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get a descriptor for the device.
|
|
||||||
func (d *DeviceInfo) Descriptor() (*Descriptor, error) {
|
|
||||||
return newDescriptor(d.dev)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open the given device for I/O.
|
|
||||||
func (d *DeviceInfo) Open() (*Device, error) {
|
|
||||||
var handle *C.libusb_device_handle
|
|
||||||
if errno := C.libusb_open(d.dev, &handle); errno != 0 {
|
|
||||||
return nil, usbError(errno)
|
|
||||||
}
|
|
||||||
|
|
||||||
return newDevice(handle), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close decrements the reference count for the device in the libusb driver
|
|
||||||
// code. It should be called exactly once!
|
|
||||||
func (d *DeviceInfo) Close() error {
|
|
||||||
if d.dev != nil {
|
|
||||||
//log.Printf("deviceInfo %p closed", d.dev)
|
|
||||||
C.libusb_unref_device(d.dev)
|
|
||||||
}
|
|
||||||
d.dev = nil
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Device struct {
|
type Device struct {
|
||||||
handle *C.libusb_device_handle
|
handle *C.libusb_device_handle
|
||||||
|
|
||||||
|
// Embed the device information for easy access
|
||||||
|
*Descriptor
|
||||||
|
|
||||||
|
// Timeouts
|
||||||
|
ControlTimeout time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDevice(handle *C.libusb_device_handle) *Device {
|
func newDevice(handle *C.libusb_device_handle, desc *Descriptor) *Device {
|
||||||
d := &Device{
|
d := &Device{
|
||||||
handle: handle,
|
handle: handle,
|
||||||
|
Descriptor: desc,
|
||||||
|
ControlTimeout: DefaultControlTimeout,
|
||||||
}
|
}
|
||||||
|
|
||||||
// :(
|
// This doesn't seem to actually get called
|
||||||
runtime.SetFinalizer(d, (*Device).Close)
|
runtime.SetFinalizer(d, (*Device).Close)
|
||||||
|
|
||||||
//log.Printf("device %p initialized", d.handle)
|
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,8 +44,6 @@ func (d *Device) Reset() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var ControlTimeout = 5 * time.Second
|
|
||||||
|
|
||||||
func (d *Device) Control(rType, request uint8, val, idx uint16, data []byte) (int, error) {
|
func (d *Device) Control(rType, request uint8, val, idx uint16, data []byte) (int, error) {
|
||||||
dataSlice := (*reflect.SliceHeader)(unsafe.Pointer(&data))
|
dataSlice := (*reflect.SliceHeader)(unsafe.Pointer(&data))
|
||||||
n := C.libusb_control_transfer(
|
n := C.libusb_control_transfer(
|
||||||
@@ -101,33 +54,39 @@ func (d *Device) Control(rType, request uint8, val, idx uint16, data []byte) (in
|
|||||||
C.uint16_t(idx),
|
C.uint16_t(idx),
|
||||||
(*C.uchar)(unsafe.Pointer(dataSlice.Data)),
|
(*C.uchar)(unsafe.Pointer(dataSlice.Data)),
|
||||||
C.uint16_t(len(data)),
|
C.uint16_t(len(data)),
|
||||||
C.uint(ControlTimeout/time.Millisecond))
|
C.uint(d.ControlTimeout/time.Millisecond))
|
||||||
if n < 0 {
|
if n < 0 {
|
||||||
return int(n), usbError(n)
|
return int(n), usbError(n)
|
||||||
}
|
}
|
||||||
return int(n), nil
|
return int(n), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Device) ActiveConfig() (int, error) {
|
// ActiveConfig returns the config id (not the index) of the active configuration.
|
||||||
|
// This corresponds to the ConfigInfo.Config field.
|
||||||
|
func (d *Device) ActiveConfig() (uint8, error) {
|
||||||
var cfg C.int
|
var cfg C.int
|
||||||
if errno := C.libusb_get_configuration(d.handle, &cfg); errno < 0 {
|
if errno := C.libusb_get_configuration(d.handle, &cfg); errno < 0 {
|
||||||
return 0, usbError(errno)
|
return 0, usbError(errno)
|
||||||
}
|
}
|
||||||
return int(cfg), nil
|
return uint8(cfg), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Device) SetConfig(cfg int) error {
|
// SetConfig attempts to change the active configuration.
|
||||||
|
// The cfg provided is the config id (not the index) of the configuration to set,
|
||||||
|
// which corresponds to the ConfigInfo.Config field.
|
||||||
|
func (d *Device) SetConfig(cfg uint8) error {
|
||||||
if errno := C.libusb_set_configuration(d.handle, C.int(cfg)); errno < 0 {
|
if errno := C.libusb_set_configuration(d.handle, C.int(cfg)); errno < 0 {
|
||||||
return usbError(errno)
|
return usbError(errno)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close the device.
|
||||||
func (d *Device) Close() error {
|
func (d *Device) Close() error {
|
||||||
if d.handle != nil {
|
if d.handle == nil {
|
||||||
//log.Printf("device %p closed", d.handle)
|
return fmt.Errorf("usb: double close on device")
|
||||||
C.libusb_unref_device(d.handle)
|
|
||||||
}
|
}
|
||||||
|
C.libusb_close(d.handle)
|
||||||
d.handle = nil
|
d.handle = nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
32
usb/usb.go
32
usb/usb.go
@@ -21,7 +21,6 @@ func (c *Context) Debug(level int) {
|
|||||||
func NewContext() *Context {
|
func NewContext() *Context {
|
||||||
c := new(Context)
|
c := new(Context)
|
||||||
|
|
||||||
//log.Printf("gousb initialized")
|
|
||||||
if errno := C.libusb_init(&c.ctx); errno != 0 {
|
if errno := C.libusb_init(&c.ctx); errno != 0 {
|
||||||
panic(usbError(errno))
|
panic(usbError(errno))
|
||||||
}
|
}
|
||||||
@@ -32,10 +31,12 @@ func NewContext() *Context {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListDevices calls each with each enumerated device. If the function returns
|
// ListDevices calls each with each enumerated device.
|
||||||
// true, the device is added to the list of *DeviceInfo to return. All of
|
// If the function returns true, the device is opened and a Device is returned if the operation succeeds.
|
||||||
// these must be Closed, even if an error is also returned.
|
// Every Device returned (whether an error is also returned or not) must be closed.
|
||||||
func (c *Context) ListDevices(each func(bus, addr int, desc *Descriptor) bool) ([]*DeviceInfo, error) {
|
// If there are any errors enumerating the devices,
|
||||||
|
// the final one is returned along with any successfully opened devices.
|
||||||
|
func (c *Context) ListDevices(each func(desc *Descriptor) bool) ([]*Device, error) {
|
||||||
var list **C.libusb_device
|
var list **C.libusb_device
|
||||||
cnt := C.libusb_get_device_list(c.ctx, &list)
|
cnt := C.libusb_get_device_list(c.ctx, &list)
|
||||||
if cnt < 0 {
|
if cnt < 0 {
|
||||||
@@ -50,25 +51,30 @@ func (c *Context) ListDevices(each func(bus, addr int, desc *Descriptor) bool) (
|
|||||||
Cap: int(cnt),
|
Cap: int(cnt),
|
||||||
}
|
}
|
||||||
|
|
||||||
ret := []*DeviceInfo{}
|
var reterr error
|
||||||
|
ret := []*Device{}
|
||||||
for _, dev := range slice {
|
for _, dev := range slice {
|
||||||
bus := int(C.libusb_get_bus_number(dev))
|
|
||||||
addr := int(C.libusb_get_device_address(dev))
|
|
||||||
desc, err := newDescriptor(dev)
|
desc, err := newDescriptor(dev)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ret, err
|
reterr = err
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
if each(bus, addr, desc) {
|
|
||||||
ret = append(ret, newDeviceInfo(dev))
|
if each(desc) {
|
||||||
|
var handle *C.libusb_device_handle
|
||||||
|
if errno := C.libusb_open(dev, &handle); errno != 0 {
|
||||||
|
reterr = err
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ret = append(ret, newDevice(handle, desc))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret, nil
|
return ret, reterr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) Close() error {
|
func (c *Context) Close() error {
|
||||||
if c.ctx != nil {
|
if c.ctx != nil {
|
||||||
C.libusb_exit(c.ctx)
|
C.libusb_exit(c.ctx)
|
||||||
//log.Printf("gousb finished")
|
|
||||||
}
|
}
|
||||||
c.ctx = nil
|
c.ctx = nil
|
||||||
return nil
|
return nil
|
||||||
|
@@ -18,9 +18,30 @@ func TestEnum(t *testing.T) {
|
|||||||
defer c.Close()
|
defer c.Close()
|
||||||
c.Debug(0)
|
c.Debug(0)
|
||||||
|
|
||||||
cnt := 0
|
logDevice := func(t *testing.T, desc *Descriptor) {
|
||||||
devs, err := c.ListDevices(func(bus, addr int, desc *Descriptor) bool {
|
t.Logf("%03d.%03d %s", desc.Bus, desc.Address, usbid.Describe(desc))
|
||||||
cnt++
|
t.Logf("- Protocol: %s", usbid.Classify(desc))
|
||||||
|
|
||||||
|
for _, cfg := range desc.Configs {
|
||||||
|
t.Logf("- %s:", cfg)
|
||||||
|
for _, alt := range cfg.Interfaces {
|
||||||
|
t.Logf(" --------------")
|
||||||
|
for _, iface := range alt {
|
||||||
|
t.Logf(" - %s", iface)
|
||||||
|
t.Logf(" - %s", usbid.Classify(iface))
|
||||||
|
for _, end := range iface.Endpoints {
|
||||||
|
t.Logf(" - %s (packet size: %d bytes)", end, end.MaxPacketSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Logf(" --------------")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
descs := []*Descriptor{}
|
||||||
|
devs, err := c.ListDevices(func(desc *Descriptor) bool {
|
||||||
|
logDevice(t, desc)
|
||||||
|
descs = append(descs, desc)
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -32,41 +53,13 @@ func TestEnum(t *testing.T) {
|
|||||||
t.Fatalf("list: %s", err)
|
t.Fatalf("list: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if got, want := len(devs), cnt; got != want {
|
if got, want := len(devs), len(descs); got != want {
|
||||||
t.Errorf("len(devs) = %d, want %d", got, want)
|
t.Fatalf("len(devs) = %d, want %d", got, want)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, dev := range devs {
|
for i := range devs {
|
||||||
desc, err := dev.Descriptor()
|
if got, want := devs[i].Descriptor, descs[i]; got != want {
|
||||||
if err != nil {
|
t.Errorf("dev[%d].Descriptor = %p, want %p", i, got, want)
|
||||||
t.Errorf("desc: %s", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
bus := dev.BusNumber()
|
|
||||||
addr := dev.Address()
|
|
||||||
|
|
||||||
t.Logf("%03d:%03d %s", bus, addr, usbid.Describe(desc))
|
|
||||||
t.Logf("- Protocol: %s", usbid.Classify(desc))
|
|
||||||
|
|
||||||
cfgs, err := dev.Configurations()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf(" - configs: %s", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, cfg := range cfgs {
|
|
||||||
t.Logf("- %s:", cfg)
|
|
||||||
for _, alt := range cfg.Interfaces {
|
|
||||||
t.Logf(" --------------")
|
|
||||||
for _, iface := range alt {
|
|
||||||
t.Logf(" - %s", iface)
|
|
||||||
t.Logf(" - %s", usbid.Classify(iface))
|
|
||||||
for _, end := range iface.Endpoints {
|
|
||||||
t.Logf(" - %s", end)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.Logf(" --------------")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user