mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-09-15 21:00:27 +00:00
275 lines
7.5 KiB
Go
275 lines
7.5 KiB
Go
//go:build with_usbip && (linux || (darwin && cgo) || windows)
|
|
|
|
package main
|
|
|
|
import (
|
|
"cmp"
|
|
"fmt"
|
|
"os"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/sagernet/sing-usbip"
|
|
"github.com/sagernet/sing/common"
|
|
E "github.com/sagernet/sing/common/exceptions"
|
|
F "github.com/sagernet/sing/common/format"
|
|
)
|
|
|
|
func runAPIUsbipDeviceList() error {
|
|
devices, err := listLocalUSBDevices()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
table := tableWriter{
|
|
header: []string{"BUSID", "VID:PID", "PRODUCT", "SERIAL"},
|
|
emptyMessage: "no local USB devices",
|
|
}
|
|
for _, device := range devices {
|
|
table.addRow(
|
|
device.Entry.Info.BusIDString(),
|
|
usbipVendorProduct(device.Entry.Info.IDVendor, device.Entry.Info.IDProduct),
|
|
device.Entry.Product,
|
|
device.Entry.Serial,
|
|
)
|
|
}
|
|
table.flush()
|
|
return nil
|
|
}
|
|
|
|
func runAPIUsbipDeviceShow(selector string) error {
|
|
devices, err := listLocalUSBDevices()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
device, err := resolveLocalUSBDevice(devices, selector)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
info := device.Entry.Info
|
|
var block blockWriter
|
|
block.addLine("busid", info.BusIDString())
|
|
block.addLine("stable id", device.StableID)
|
|
block.addLine("backend", device.Backend.String())
|
|
if device.Entry.Product != "" {
|
|
block.addLine("product", device.Entry.Product)
|
|
}
|
|
if device.Entry.Serial != "" {
|
|
block.addLine("serial", device.Entry.Serial)
|
|
}
|
|
block.addLine("vendor id", fmt.Sprintf("%04x", info.IDVendor))
|
|
block.addLine("product id", fmt.Sprintf("%04x", info.IDProduct))
|
|
block.addLine("device version", fmt.Sprintf("%x.%02x", info.BCDDevice>>8, info.BCDDevice&0xff))
|
|
block.addLine("bus", F.ToString(info.BusNum))
|
|
block.addLine("device", F.ToString(info.DevNum))
|
|
block.addLine("speed", usbipSpeedString(info.Speed))
|
|
block.addLine("device class", usbipDeviceClassString(info.BDeviceClass))
|
|
block.addLine("device subclass", fmt.Sprintf("%02x", info.BDeviceSubClass))
|
|
block.addLine("device protocol", fmt.Sprintf("%02x", info.BDeviceProtocol))
|
|
if info.BNumConfigurations > 0 {
|
|
block.addLine("configurations", F.ToString(info.BNumConfigurations, " (active #", info.BConfigurationValue, ")"))
|
|
}
|
|
for index, deviceInterface := range device.Entry.Interfaces {
|
|
block.addLine(F.ToString("interface ", index), fmt.Sprintf(
|
|
"class %02x subclass %02x protocol %02x",
|
|
deviceInterface.BInterfaceClass,
|
|
deviceInterface.BInterfaceSubClass,
|
|
deviceInterface.BInterfaceProtocol,
|
|
))
|
|
}
|
|
block.flush()
|
|
return nil
|
|
}
|
|
|
|
// ListLocalDevices filters hubs on linux and darwin but not on windows.
|
|
func listLocalUSBDevices() ([]usbip.LocalDeviceInfo, error) {
|
|
devices, err := usbip.ListLocalDevices()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
devices = common.Filter(devices, func(it usbip.LocalDeviceInfo) bool {
|
|
return it.Entry.Info.BDeviceClass != 0x09
|
|
})
|
|
slices.SortFunc(devices, func(a usbip.LocalDeviceInfo, b usbip.LocalDeviceInfo) int {
|
|
return compareBusID(a.Entry.Info.BusIDString(), b.Entry.Info.BusIDString())
|
|
})
|
|
return devices, nil
|
|
}
|
|
|
|
func resolveLocalUSBDevice(devices []usbip.LocalDeviceInfo, selector string) (usbip.LocalDeviceInfo, error) {
|
|
index := slices.IndexFunc(devices, func(it usbip.LocalDeviceInfo) bool {
|
|
return it.Entry.Info.BusIDString() == selector
|
|
})
|
|
if index != -1 {
|
|
return devices[index], nil
|
|
}
|
|
vendorID, productID, serial, valid := parseLocalUSBDeviceSelector(selector)
|
|
if !valid {
|
|
return usbip.LocalDeviceInfo{}, E.New("no local USB device matches ", selector)
|
|
}
|
|
matches := common.Filter(devices, func(it usbip.LocalDeviceInfo) bool {
|
|
if it.Entry.Info.IDVendor != vendorID || it.Entry.Info.IDProduct != productID {
|
|
return false
|
|
}
|
|
return serial == "" || it.Entry.Serial == serial
|
|
})
|
|
switch len(matches) {
|
|
case 0:
|
|
return usbip.LocalDeviceInfo{}, E.New("no local USB device matches ", selector)
|
|
case 1:
|
|
return matches[0], nil
|
|
}
|
|
writeLocalUSBDeviceMatches(matches)
|
|
return usbip.LocalDeviceInfo{}, E.New(selector, " matches ", len(matches), " devices")
|
|
}
|
|
|
|
func parseLocalUSBDeviceSelector(selector string) (vendorID uint16, productID uint16, serial string, valid bool) {
|
|
parts := strings.SplitN(selector, ":", 3)
|
|
if len(parts) < 2 {
|
|
return 0, 0, "", false
|
|
}
|
|
vendorID, valid = parseUSBIdentifier(parts[0])
|
|
if !valid {
|
|
return 0, 0, "", false
|
|
}
|
|
productID, valid = parseUSBIdentifier(parts[1])
|
|
if !valid {
|
|
return 0, 0, "", false
|
|
}
|
|
if len(parts) == 3 {
|
|
serial = parts[2]
|
|
if serial == "" {
|
|
return 0, 0, "", false
|
|
}
|
|
}
|
|
return vendorID, productID, serial, true
|
|
}
|
|
|
|
func parseUSBIdentifier(value string) (uint16, bool) {
|
|
if len(value) != 4 {
|
|
return 0, false
|
|
}
|
|
parsed, err := strconv.ParseUint(value, 16, 16)
|
|
if err != nil {
|
|
return 0, false
|
|
}
|
|
return uint16(parsed), true
|
|
}
|
|
|
|
func writeLocalUSBDeviceMatches(matches []usbip.LocalDeviceInfo) {
|
|
busIDWidth := 0
|
|
productWidth := 0
|
|
for _, device := range matches {
|
|
busIDWidth = max(busIDWidth, len(device.Entry.Info.BusIDString()))
|
|
productWidth = max(productWidth, len(device.Entry.Product))
|
|
}
|
|
var output strings.Builder
|
|
for _, device := range matches {
|
|
output.WriteString(" ")
|
|
output.WriteString(padUSBIPCell(device.Entry.Info.BusIDString(), busIDWidth))
|
|
output.WriteString(" ")
|
|
output.WriteString(usbipVendorProduct(device.Entry.Info.IDVendor, device.Entry.Info.IDProduct))
|
|
if device.Entry.Product != "" || device.Entry.Serial != "" {
|
|
output.WriteString(" ")
|
|
output.WriteString(padUSBIPCell(device.Entry.Product, productWidth))
|
|
}
|
|
if device.Entry.Serial != "" {
|
|
output.WriteString(" (serial ")
|
|
output.WriteString(device.Entry.Serial)
|
|
output.WriteString(")")
|
|
}
|
|
output.WriteString("\n")
|
|
}
|
|
os.Stderr.WriteString(output.String())
|
|
}
|
|
|
|
func padUSBIPCell(value string, width int) string {
|
|
if len(value) >= width {
|
|
return value
|
|
}
|
|
return value + strings.Repeat(" ", width-len(value))
|
|
}
|
|
|
|
func compareBusID(a string, b string) int {
|
|
for len(a) > 0 && len(b) > 0 {
|
|
aDigits := usbipDigitPrefix(a)
|
|
bDigits := usbipDigitPrefix(b)
|
|
if aDigits > 0 && bDigits > 0 {
|
|
aValue, aErr := strconv.ParseUint(a[:aDigits], 10, 64)
|
|
bValue, bErr := strconv.ParseUint(b[:bDigits], 10, 64)
|
|
if aErr == nil && bErr == nil {
|
|
if aValue != bValue {
|
|
return cmp.Compare(aValue, bValue)
|
|
}
|
|
a = a[aDigits:]
|
|
b = b[bDigits:]
|
|
continue
|
|
}
|
|
}
|
|
if a[0] != b[0] {
|
|
return cmp.Compare(a[0], b[0])
|
|
}
|
|
a = a[1:]
|
|
b = b[1:]
|
|
}
|
|
return cmp.Compare(len(a), len(b))
|
|
}
|
|
|
|
func usbipDigitPrefix(value string) int {
|
|
index := 0
|
|
for index < len(value) && value[index] >= '0' && value[index] <= '9' {
|
|
index++
|
|
}
|
|
return index
|
|
}
|
|
|
|
func usbipSpeedString(speed uint32) string {
|
|
switch speed {
|
|
case usbip.SpeedLow:
|
|
return "low (1.5 Mbps)"
|
|
case usbip.SpeedFull:
|
|
return "full (12 Mbps)"
|
|
case usbip.SpeedHigh:
|
|
return "high (480 Mbps)"
|
|
case usbip.SpeedSuper:
|
|
return "super (5 Gbps)"
|
|
case usbip.SpeedSuperPlus:
|
|
return "super+ (10 Gbps)"
|
|
default:
|
|
return F.ToString(speed)
|
|
}
|
|
}
|
|
|
|
var usbipDeviceClassNames = map[uint8]string{
|
|
0x00: "defined at interface level",
|
|
0x01: "audio",
|
|
0x02: "communications",
|
|
0x03: "human interface device",
|
|
0x05: "physical",
|
|
0x06: "image",
|
|
0x07: "printer",
|
|
0x08: "mass storage",
|
|
0x09: "hub",
|
|
0x0a: "cdc data",
|
|
0x0b: "smart card",
|
|
0x0d: "content security",
|
|
0x0e: "video",
|
|
0x0f: "personal healthcare",
|
|
0x10: "audio/video",
|
|
0x11: "billboard",
|
|
0x12: "usb type-c bridge",
|
|
0xdc: "diagnostic",
|
|
0xe0: "wireless controller",
|
|
0xef: "miscellaneous",
|
|
0xfe: "application specific",
|
|
0xff: "vendor specific",
|
|
}
|
|
|
|
func usbipDeviceClassString(deviceClass uint8) string {
|
|
name, found := usbipDeviceClassNames[deviceClass]
|
|
if !found {
|
|
return fmt.Sprintf("%02x", deviceClass)
|
|
}
|
|
return fmt.Sprintf("%02x (%s)", deviceClass, name)
|
|
}
|