37065daccf
Address `golangci-lint` modernize and unused warnings:
- replace map copy loops with `maps.Copy`
- collapse `if x > y { x = y }` clamps to `min`/`max`
- switch `for i := 0; i < n; i++` to `for i := range n`
- use `t.Context()` in the relay handoff test
- delete the unused `removeOnce` field and `openBinaryDevice` helper
284 lines
7.2 KiB
Go
284 lines
7.2 KiB
Go
//go:build linux || (darwin && cgo)
|
|
|
|
package usbip
|
|
|
|
import (
|
|
"maps"
|
|
"sync"
|
|
|
|
"github.com/sagernet/sing-box/option"
|
|
)
|
|
|
|
// clientAssignment runs in two modes that share active-busid tracking:
|
|
// matched (len(targets) > 0) binds each target to at most one busid;
|
|
// import-all (len(targets) == 0) marks every advertised busid as desired.
|
|
type clientAssignment struct {
|
|
access sync.Mutex
|
|
|
|
targets []clientTarget
|
|
assigned []string
|
|
matchedKnownKeys map[string]DeviceKey
|
|
|
|
allDesired map[string]struct{}
|
|
registered map[string]struct{}
|
|
|
|
activeBusIDs map[string]struct{}
|
|
}
|
|
|
|
func newClientAssignment(matches []option.USBIPDeviceMatch) *clientAssignment {
|
|
var targets []clientTarget
|
|
if len(matches) > 0 {
|
|
seenFixed := make(map[string]struct{})
|
|
targets = make([]clientTarget, 0, len(matches))
|
|
for _, m := range matches {
|
|
if m.BusID != "" && m.VendorID == 0 && m.ProductID == 0 && m.Serial == "" {
|
|
if _, seen := seenFixed[m.BusID]; seen {
|
|
continue
|
|
}
|
|
seenFixed[m.BusID] = struct{}{}
|
|
targets = append(targets, clientTarget{fixedBusID: m.BusID})
|
|
continue
|
|
}
|
|
targets = append(targets, clientTarget{match: m})
|
|
}
|
|
}
|
|
return &clientAssignment{
|
|
targets: targets,
|
|
allDesired: make(map[string]struct{}),
|
|
registered: make(map[string]struct{}),
|
|
activeBusIDs: make(map[string]struct{}),
|
|
}
|
|
}
|
|
|
|
func (a *clientAssignment) Matched() bool {
|
|
return len(a.targets) > 0
|
|
}
|
|
|
|
func (a *clientAssignment) SetActive(busid string, active bool) {
|
|
a.access.Lock()
|
|
defer a.access.Unlock()
|
|
if active {
|
|
a.activeBusIDs[busid] = struct{}{}
|
|
} else {
|
|
delete(a.activeBusIDs, busid)
|
|
}
|
|
}
|
|
|
|
func (a *clientAssignment) ApplyMatched(entries []DeviceEntry, knownKeys map[string]DeviceKey) (next []string, previous []string) {
|
|
a.access.Lock()
|
|
defer a.access.Unlock()
|
|
if len(a.targets) == 0 {
|
|
return nil, nil
|
|
}
|
|
if a.assigned == nil {
|
|
a.assigned = make([]string, len(a.targets))
|
|
}
|
|
assignmentKeys := a.matchedKeysForAssignmentLocked(entries, knownKeys)
|
|
activeCurrent := a.activeCurrentAssignmentsLocked(a.assigned, assignmentKeys)
|
|
nextAssigned := assignMatchedBusIDsWithRetained(a.targets, a.assigned, entries, assignmentKeys, activeCurrent)
|
|
prev := append([]string(nil), a.assigned...)
|
|
a.assigned = nextAssigned
|
|
a.retainMatchedKnownKeysLocked(assignmentKeys, entries, nextAssigned)
|
|
return nextAssigned, prev
|
|
}
|
|
|
|
// ApplyAll keeps no-longer-desired-but-active busids registered so the
|
|
// runBusIDLoop exits naturally after the active session ends, via
|
|
// IsRetryDesired returning false.
|
|
func (a *clientAssignment) ApplyAll(entries []DeviceEntry) (start []string, stop []string) {
|
|
desired := make(map[string]struct{}, len(entries))
|
|
for i := range entries {
|
|
busid := entries[i].Info.BusIDString()
|
|
if busid == "" {
|
|
continue
|
|
}
|
|
desired[busid] = struct{}{}
|
|
}
|
|
a.access.Lock()
|
|
defer a.access.Unlock()
|
|
a.allDesired = desired
|
|
for busid := range a.registered {
|
|
if _, ok := desired[busid]; ok {
|
|
continue
|
|
}
|
|
if _, active := a.activeBusIDs[busid]; active {
|
|
continue
|
|
}
|
|
stop = append(stop, busid)
|
|
delete(a.registered, busid)
|
|
}
|
|
for busid := range desired {
|
|
if _, ok := a.registered[busid]; ok {
|
|
continue
|
|
}
|
|
start = append(start, busid)
|
|
a.registered[busid] = struct{}{}
|
|
}
|
|
return start, stop
|
|
}
|
|
|
|
func (a *clientAssignment) IsRetryDesired(busid string) bool {
|
|
a.access.Lock()
|
|
defer a.access.Unlock()
|
|
if _, registered := a.registered[busid]; !registered {
|
|
return false
|
|
}
|
|
_, desired := a.allDesired[busid]
|
|
return desired
|
|
}
|
|
|
|
func (a *clientAssignment) matchedKeysForAssignmentLocked(entries []DeviceEntry, knownKeys map[string]DeviceKey) map[string]DeviceKey {
|
|
if len(a.matchedKnownKeys) == 0 && len(entries) == 0 && len(knownKeys) == 0 {
|
|
return nil
|
|
}
|
|
assignmentKeys := make(map[string]DeviceKey, len(a.matchedKnownKeys)+len(entries)+len(knownKeys))
|
|
maps.Copy(assignmentKeys, a.matchedKnownKeys)
|
|
for i := range entries {
|
|
key := entryDeviceKey(entries[i])
|
|
if key.BusID == "" {
|
|
continue
|
|
}
|
|
assignmentKeys[key.BusID] = key
|
|
}
|
|
for busid, key := range knownKeys {
|
|
if busid == "" {
|
|
continue
|
|
}
|
|
assignmentKeys[busid] = key
|
|
}
|
|
return assignmentKeys
|
|
}
|
|
|
|
func (a *clientAssignment) retainMatchedKnownKeysLocked(assignmentKeys map[string]DeviceKey, entries []DeviceEntry, assigned []string) {
|
|
if len(assignmentKeys) == 0 {
|
|
a.matchedKnownKeys = nil
|
|
return
|
|
}
|
|
retained := make(map[string]DeviceKey, len(entries)+len(assigned))
|
|
for i := range entries {
|
|
busid := entries[i].Info.BusIDString()
|
|
if busid == "" {
|
|
continue
|
|
}
|
|
if key, ok := assignmentKeys[busid]; ok {
|
|
retained[busid] = key
|
|
}
|
|
}
|
|
for _, busid := range assigned {
|
|
if busid == "" {
|
|
continue
|
|
}
|
|
if key, ok := assignmentKeys[busid]; ok {
|
|
retained[busid] = key
|
|
}
|
|
}
|
|
if len(retained) == 0 {
|
|
a.matchedKnownKeys = nil
|
|
return
|
|
}
|
|
a.matchedKnownKeys = retained
|
|
}
|
|
|
|
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
|
|
}
|
|
for j := range entries {
|
|
key := entryDeviceKey(entries[j])
|
|
if _, claimed := reserved[key.BusID]; claimed {
|
|
continue
|
|
}
|
|
if matches(target.match, key) {
|
|
nextAssigned[i] = key.BusID
|
|
reserved[key.BusID] = struct{}{}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return nextAssigned
|
|
}
|
|
|
|
func (a *clientAssignment) activeCurrentAssignmentsLocked(current []string, knownKeys map[string]DeviceKey) map[string]struct{} {
|
|
if len(knownKeys) == 0 {
|
|
return nil
|
|
}
|
|
var activeCurrent map[string]struct{}
|
|
for _, busid := range current {
|
|
if busid == "" {
|
|
continue
|
|
}
|
|
if _, ok := knownKeys[busid]; !ok {
|
|
continue
|
|
}
|
|
if _, active := a.activeBusIDs[busid]; !active {
|
|
continue
|
|
}
|
|
if activeCurrent == nil {
|
|
activeCurrent = make(map[string]struct{})
|
|
}
|
|
activeCurrent[busid] = struct{}{}
|
|
}
|
|
return activeCurrent
|
|
}
|