Files
sing-box/service/usbip/control_protocol.go
T

271 lines
8.2 KiB
Go

//go:build linux || (darwin && cgo) || windows
package usbip
import (
"encoding/binary"
"encoding/json"
"io"
"slices"
E "github.com/sagernet/sing/common/exceptions"
)
const (
controlProtocolVersion uint8 = 1
controlFrameHello uint8 = 1
controlFrameAck uint8 = 2
controlFramePing uint8 = 3
controlFramePong uint8 = 4
controlFrameDeviceSnapshot uint8 = 5
controlPrefaceSize = 8
controlFrameSize = 4
maxControlPayloadLength = 64<<10 - 1
deviceStateAvailable = "available"
deviceStateBusy = "busy"
deviceStateUnavailable = "unavailable"
backendIDLinuxSysfs = "linux-sysfs"
backendIDDarwinIOKit = "darwin-iokit"
backendIDWindowsVBoxUSB = "windows-vboxusb"
)
var controlPreface = [controlPrefaceSize]byte{'S', 'B', 'U', 'S', 'B', 'I', 'P', '1'}
type controlFrame struct {
Type uint8
Version uint8
PayloadLength uint16
}
type controlMessage struct {
Frame controlFrame
Payload []byte
}
type ControlDeviceInterface struct {
Class uint8 `json:"class"`
SubClass uint8 `json:"subclass"`
Protocol uint8 `json:"protocol"`
}
type ControlDeviceInfo struct {
BusID string `json:"busid"`
StableID string `json:"stable_id,omitempty"`
Backend string `json:"backend,omitempty"`
Path string `json:"path,omitempty"`
Serial string `json:"serial,omitempty"`
VendorID uint16 `json:"vendor_id"`
ProductID uint16 `json:"product_id"`
BCDDevice uint16 `json:"bcd_device,omitempty"`
Speed uint32 `json:"speed"`
DeviceClass uint8 `json:"device_class"`
DeviceSubClass uint8 `json:"device_subclass"`
DeviceProtocol uint8 `json:"device_protocol"`
ConfigurationValue uint8 `json:"configuration_value"`
NumConfigurations uint8 `json:"num_configurations"`
NumInterfaces uint8 `json:"num_interfaces"`
Interfaces []ControlDeviceInterface `json:"interfaces,omitempty"`
State string `json:"state"`
StatusCode int `json:"status_code,omitempty"`
StatusReason string `json:"status_reason,omitempty"`
}
type controlDeviceSnapshot struct {
Devices []ControlDeviceInfo `json:"devices"`
}
// The returned payload is only valid until the next call.
type controlReader struct {
scratch []byte
}
func (cr *controlReader) read(r io.Reader) (controlMessage, error) {
var raw [controlFrameSize]byte
_, err := io.ReadFull(r, raw[:])
if err != nil {
return controlMessage{}, err
}
frame := controlFrame{
Type: raw[0],
Version: raw[1],
PayloadLength: binary.BigEndian.Uint16(raw[2:4]),
}
var payload []byte
if frame.PayloadLength > 0 {
if cap(cr.scratch) < int(frame.PayloadLength) {
cr.scratch = make([]byte, frame.PayloadLength)
}
payload = cr.scratch[:frame.PayloadLength]
_, err = io.ReadFull(r, payload)
if err != nil {
return controlMessage{}, err
}
}
return controlMessage{Frame: frame, Payload: payload}, nil
}
func writeControlMessage(w io.Writer, frame controlFrame, payload any) error {
rawPayload, err := marshalControlPayload(payload)
if err != nil {
return err
}
if len(rawPayload) > maxControlPayloadLength {
return E.New("control payload too large: ", len(rawPayload))
}
frame.PayloadLength = uint16(len(rawPayload))
var raw [controlFrameSize]byte
raw[0] = frame.Type
raw[1] = frame.Version
binary.BigEndian.PutUint16(raw[2:4], frame.PayloadLength)
_, err = w.Write(raw[:])
if err != nil {
return err
}
if len(rawPayload) == 0 {
return nil
}
_, err = w.Write(rawPayload)
return err
}
func marshalControlPayload(payload any) ([]byte, error) {
switch value := payload.(type) {
case nil:
return nil, nil
case []byte:
return value, nil
default:
return json.Marshal(value)
}
}
func unmarshalControlPayload(payload []byte, value any) error {
if len(payload) == 0 {
return E.New("missing control payload")
}
return json.Unmarshal(payload, value)
}
func controlDeviceInfoFromEntry(entry DeviceEntry, backend string, stableID string, state string, statusCode int, statusReason string) ControlDeviceInfo {
interfaces := make([]ControlDeviceInterface, len(entry.Interfaces))
for i := range entry.Interfaces {
interfaces[i] = ControlDeviceInterface{
Class: entry.Interfaces[i].BInterfaceClass,
SubClass: entry.Interfaces[i].BInterfaceSubClass,
Protocol: entry.Interfaces[i].BInterfaceProtocol,
}
}
if state == "" {
state = deviceStateAvailable
}
serial := entry.Serial
if serial == "" {
serial = entry.Info.SerialString()
}
return ControlDeviceInfo{
BusID: entry.Info.BusIDString(),
StableID: stableID,
Backend: backend,
Path: cstring(entry.Info.Path[:]),
Serial: serial,
VendorID: entry.Info.IDVendor,
ProductID: entry.Info.IDProduct,
BCDDevice: entry.Info.BCDDevice,
Speed: entry.Info.Speed,
DeviceClass: entry.Info.BDeviceClass,
DeviceSubClass: entry.Info.BDeviceSubClass,
DeviceProtocol: entry.Info.BDeviceProtocol,
ConfigurationValue: entry.Info.BConfigurationValue,
NumConfigurations: entry.Info.BNumConfigurations,
NumInterfaces: entry.Info.BNumInterfaces,
Interfaces: interfaces,
State: state,
StatusCode: statusCode,
StatusReason: statusReason,
}
}
func controlDeviceInfoMap(devices []ControlDeviceInfo) map[string]ControlDeviceInfo {
out := make(map[string]ControlDeviceInfo, len(devices))
for _, device := range devices {
if device.BusID == "" {
continue
}
out[device.BusID] = device
}
return out
}
func sortedControlDeviceInfoValues(devices map[string]ControlDeviceInfo) []ControlDeviceInfo {
busids := make([]string, 0, len(devices))
for busid := range devices {
busids = append(busids, busid)
}
slices.Sort(busids)
out := make([]ControlDeviceInfo, 0, len(busids))
for _, busid := range busids {
out = append(out, devices[busid])
}
return out
}
func controlDeviceInfoToEntries(devices []ControlDeviceInfo, availableOnly bool) []DeviceEntry {
entries := make([]DeviceEntry, 0, len(devices))
for _, device := range devices {
if availableOnly && device.State != "" && device.State != deviceStateAvailable {
continue
}
var info DeviceInfoTruncated
encodePathField(&info.Path, device.Path, device.Serial)
copy(info.BusID[:], device.BusID)
info.Speed = device.Speed
info.IDVendor = device.VendorID
info.IDProduct = device.ProductID
info.BCDDevice = device.BCDDevice
info.BDeviceClass = device.DeviceClass
info.BDeviceSubClass = device.DeviceSubClass
info.BDeviceProtocol = device.DeviceProtocol
info.BConfigurationValue = device.ConfigurationValue
info.BNumConfigurations = device.NumConfigurations
info.BNumInterfaces = device.NumInterfaces
interfaces := make([]DeviceInterface, len(device.Interfaces))
for i := range device.Interfaces {
interfaces[i] = DeviceInterface{
BInterfaceClass: device.Interfaces[i].Class,
BInterfaceSubClass: device.Interfaces[i].SubClass,
BInterfaceProtocol: device.Interfaces[i].Protocol,
}
}
entries = append(entries, DeviceEntry{Info: info, Interfaces: interfaces, Serial: device.Serial})
}
return entries
}
func controlDeviceInfoEqual(a, b ControlDeviceInfo) bool {
if a.BusID != b.BusID ||
a.StableID != b.StableID ||
a.Backend != b.Backend ||
a.Path != b.Path ||
a.Serial != b.Serial ||
a.VendorID != b.VendorID ||
a.ProductID != b.ProductID ||
a.BCDDevice != b.BCDDevice ||
a.Speed != b.Speed ||
a.DeviceClass != b.DeviceClass ||
a.DeviceSubClass != b.DeviceSubClass ||
a.DeviceProtocol != b.DeviceProtocol ||
a.ConfigurationValue != b.ConfigurationValue ||
a.NumConfigurations != b.NumConfigurations ||
a.NumInterfaces != b.NumInterfaces ||
a.State != b.State ||
a.StatusCode != b.StatusCode ||
a.StatusReason != b.StatusReason {
return false
}
return slices.Equal(a.Interfaces, b.Interfaces)
}