Files
sing-box/service/usbip/shared.go
T
2026-06-09 10:42:25 +08:00

158 lines
3.5 KiB
Go

//go:build linux || (darwin && cgo)
package usbip
import (
"context"
"fmt"
"net"
"strings"
"time"
"github.com/sagernet/sing-box/option"
)
type clientTarget struct {
fixedBusID string
match option.USBIPDeviceMatch
}
func (t clientTarget) description() string {
if t.fixedBusID != "" {
return describeMatch(option.USBIPDeviceMatch{BusID: t.fixedBusID})
}
return describeMatch(t.match)
}
func isBusIDOnlyMatch(m option.USBIPDeviceMatch) bool {
return m.BusID != "" && m.VendorID == 0 && m.ProductID == 0 && m.Serial == ""
}
func assignMatchedBusIDsWithRetained(
targets []clientTarget,
current []string,
entries []DeviceEntry,
knownKeys map[string]DeviceKey,
activeCurrent map[string]struct{},
) []string {
if len(targets) == 0 {
return nil
}
keysByBusID := make(map[string]DeviceKey, len(entries))
for i := range entries {
busid := entries[i].Info.BusIDString()
if busid == "" {
continue
}
keysByBusID[busid] = entryDeviceKey(entries[i])
}
currentKey := func(busid string) (DeviceKey, bool) {
if key, ok := keysByBusID[busid]; ok {
return key, true
}
if _, active := activeCurrent[busid]; !active {
return DeviceKey{}, false
}
key, ok := knownKeys[busid]
return key, ok
}
nextAssigned := make([]string, len(targets))
reserved := make(map[string]struct{}, len(targets))
for i, target := range targets {
if target.fixedBusID == "" {
continue
}
if _, ok := keysByBusID[target.fixedBusID]; ok {
nextAssigned[i] = target.fixedBusID
reserved[target.fixedBusID] = struct{}{}
continue
}
if i >= len(current) || current[i] != target.fixedBusID {
continue
}
if _, ok := currentKey(target.fixedBusID); ok {
nextAssigned[i] = target.fixedBusID
reserved[target.fixedBusID] = struct{}{}
}
}
for i, target := range targets {
if target.fixedBusID != "" || i >= len(current) || current[i] == "" {
continue
}
if _, ok := reserved[current[i]]; ok {
continue
}
key, ok := currentKey(current[i])
if !ok || !matches(target.match, key) {
continue
}
nextAssigned[i] = current[i]
reserved[current[i]] = struct{}{}
}
for i, target := range targets {
if target.fixedBusID != "" || nextAssigned[i] != "" {
continue
}
nextAssigned[i] = firstMatchingUnclaimedBusID(target.match, entries, reserved)
if nextAssigned[i] != "" {
reserved[nextAssigned[i]] = struct{}{}
}
}
return nextAssigned
}
func firstMatchingUnclaimedBusID(match option.USBIPDeviceMatch, entries []DeviceEntry, reserved map[string]struct{}) string {
for i := range entries {
key := entryDeviceKey(entries[i])
if _, claimed := reserved[key.BusID]; claimed {
continue
}
if matches(match, key) {
return key.BusID
}
}
return ""
}
func sleepCtx(ctx context.Context, d time.Duration) bool {
t := time.NewTimer(d)
defer t.Stop()
select {
case <-ctx.Done():
return false
case <-t.C:
return true
}
}
func closeConnOnContextDone(ctx context.Context, conn net.Conn) func() {
done := make(chan struct{})
go func() {
select {
case <-ctx.Done():
_ = conn.Close()
case <-done:
}
}()
return func() {
close(done)
}
}
func describeMatch(m option.USBIPDeviceMatch) string {
var parts []string
if m.BusID != "" {
parts = append(parts, "busid="+m.BusID)
}
if m.VendorID != 0 {
parts = append(parts, fmt.Sprintf("vendor_id=0x%04x", uint16(m.VendorID)))
}
if m.ProductID != 0 {
parts = append(parts, fmt.Sprintf("product_id=0x%04x", uint16(m.ProductID)))
}
if m.Serial != "" {
parts = append(parts, "serial="+m.Serial)
}
return "{" + strings.Join(parts, ",") + "}"
}