418 lines
9.6 KiB
Go
418 lines
9.6 KiB
Go
package usbip
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
)
|
|
|
|
// Protocol version
|
|
const ProtocolVersion = 0x0111
|
|
|
|
// Management phase opcodes
|
|
const (
|
|
OpReqDevlist = 0x8005
|
|
OpRepDevlist = 0x0005
|
|
OpReqImport = 0x8003
|
|
OpRepImport = 0x0003
|
|
)
|
|
|
|
// Data transfer phase commands
|
|
const (
|
|
CmdSubmit = 0x00000001
|
|
CmdUnlink = 0x00000002
|
|
RetSubmit = 0x00000003
|
|
RetUnlink = 0x00000004
|
|
)
|
|
|
|
// Transfer directions
|
|
const (
|
|
DirOut = 0
|
|
DirIn = 1
|
|
)
|
|
|
|
// USB device speeds
|
|
const (
|
|
SpeedUnknown = 0
|
|
SpeedLow = 1
|
|
SpeedFull = 2
|
|
SpeedHigh = 3
|
|
SpeedWireless = 4
|
|
SpeedSuper = 5
|
|
SpeedSuperPlus = 6
|
|
)
|
|
|
|
// OpHeader is the 8-byte header for management messages
|
|
type OpHeader struct {
|
|
Version uint16
|
|
Command uint16
|
|
Status uint32
|
|
}
|
|
|
|
// DeviceDescriptor describes a USB device in USB/IP protocol
|
|
type DeviceDescriptor struct {
|
|
Path [256]byte
|
|
BusID [32]byte
|
|
BusNum uint32
|
|
DevNum uint32
|
|
Speed uint32
|
|
IDVendor uint16
|
|
IDProduct uint16
|
|
BcdDevice uint16
|
|
BDeviceClass uint8
|
|
BDeviceSubClass uint8
|
|
BDeviceProtocol uint8
|
|
BConfigurationValue uint8
|
|
BNumConfigurations uint8
|
|
BNumInterfaces uint8
|
|
}
|
|
|
|
// InterfaceDescriptor describes a USB interface
|
|
type InterfaceDescriptor struct {
|
|
BInterfaceClass uint8
|
|
BInterfaceSubClass uint8
|
|
BInterfaceProtocol uint8
|
|
Padding uint8
|
|
}
|
|
|
|
// URBHeader is the 48-byte common header for USB/IP transfer messages
|
|
type URBHeader struct {
|
|
Command uint32
|
|
SeqNum uint32
|
|
DevID uint32
|
|
Direction uint32
|
|
Endpoint uint32
|
|
}
|
|
|
|
// CmdSubmitBody follows URBHeader for USBIP_CMD_SUBMIT
|
|
type CmdSubmitBody struct {
|
|
TransferFlags uint32
|
|
TransferBufferLen uint32
|
|
StartFrame uint32
|
|
NumberOfPackets uint32
|
|
Interval uint32
|
|
Setup [8]byte
|
|
}
|
|
|
|
// RetSubmitBody follows URBHeader for USBIP_RET_SUBMIT
|
|
type RetSubmitBody struct {
|
|
Status int32
|
|
ActualLength uint32
|
|
StartFrame uint32
|
|
NumberOfPackets uint32
|
|
ErrorCount uint32
|
|
Padding [8]byte
|
|
}
|
|
|
|
// CmdUnlinkBody follows URBHeader for USBIP_CMD_UNLINK
|
|
type CmdUnlinkBody struct {
|
|
UnlinkSeqNum uint32
|
|
Padding [24]byte
|
|
}
|
|
|
|
// RetUnlinkBody follows URBHeader for USBIP_RET_UNLINK
|
|
type RetUnlinkBody struct {
|
|
Status int32
|
|
Padding [24]byte
|
|
}
|
|
|
|
// ISOPacketDescriptor for isochronous transfers
|
|
type ISOPacketDescriptor struct {
|
|
Offset uint32
|
|
Length uint32
|
|
ActualLength uint32
|
|
Status uint32
|
|
}
|
|
|
|
// --- Encoding/Decoding helpers ---
|
|
|
|
// WriteOpHeader writes an operation header
|
|
func WriteOpHeader(w io.Writer, cmd uint16, status uint32) error {
|
|
h := OpHeader{Version: ProtocolVersion, Command: cmd, Status: status}
|
|
return binary.Write(w, binary.BigEndian, &h)
|
|
}
|
|
|
|
// ReadOpHeader reads an operation header
|
|
func ReadOpHeader(r io.Reader) (*OpHeader, error) {
|
|
h := &OpHeader{}
|
|
if err := binary.Read(r, binary.BigEndian, h); err != nil {
|
|
return nil, err
|
|
}
|
|
return h, nil
|
|
}
|
|
|
|
// WriteDeviceDescriptor writes a device descriptor
|
|
func WriteDeviceDescriptor(w io.Writer, d *DeviceDescriptor) error {
|
|
return binary.Write(w, binary.BigEndian, d)
|
|
}
|
|
|
|
// ReadDeviceDescriptor reads a device descriptor
|
|
func ReadDeviceDescriptor(r io.Reader) (*DeviceDescriptor, error) {
|
|
d := &DeviceDescriptor{}
|
|
if err := binary.Read(r, binary.BigEndian, d); err != nil {
|
|
return nil, err
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
// WriteInterfaceDescriptor writes an interface descriptor
|
|
func WriteInterfaceDescriptor(w io.Writer, d *InterfaceDescriptor) error {
|
|
return binary.Write(w, binary.BigEndian, d)
|
|
}
|
|
|
|
// ReadURBHeader reads a URB header
|
|
func ReadURBHeader(r io.Reader) (*URBHeader, error) {
|
|
h := &URBHeader{}
|
|
if err := binary.Read(r, binary.BigEndian, h); err != nil {
|
|
return nil, err
|
|
}
|
|
return h, nil
|
|
}
|
|
|
|
// WriteURBHeader writes a URB header
|
|
func WriteURBHeader(w io.Writer, h *URBHeader) error {
|
|
return binary.Write(w, binary.BigEndian, h)
|
|
}
|
|
|
|
// ReadCmdSubmit reads a CMD_SUBMIT body (after URB header)
|
|
func ReadCmdSubmit(r io.Reader) (*CmdSubmitBody, error) {
|
|
b := &CmdSubmitBody{}
|
|
if err := binary.Read(r, binary.BigEndian, b); err != nil {
|
|
return nil, err
|
|
}
|
|
return b, nil
|
|
}
|
|
|
|
// WriteCmdSubmit writes a CMD_SUBMIT body
|
|
func WriteCmdSubmit(w io.Writer, b *CmdSubmitBody) error {
|
|
return binary.Write(w, binary.BigEndian, b)
|
|
}
|
|
|
|
// ReadRetSubmit reads a RET_SUBMIT body
|
|
func ReadRetSubmit(r io.Reader) (*RetSubmitBody, error) {
|
|
b := &RetSubmitBody{}
|
|
if err := binary.Read(r, binary.BigEndian, b); err != nil {
|
|
return nil, err
|
|
}
|
|
return b, nil
|
|
}
|
|
|
|
// WriteRetSubmit writes a RET_SUBMIT body
|
|
func WriteRetSubmit(w io.Writer, b *RetSubmitBody) error {
|
|
return binary.Write(w, binary.BigEndian, b)
|
|
}
|
|
|
|
// ReadCmdUnlink reads a CMD_UNLINK body
|
|
func ReadCmdUnlink(r io.Reader) (*CmdUnlinkBody, error) {
|
|
b := &CmdUnlinkBody{}
|
|
if err := binary.Read(r, binary.BigEndian, b); err != nil {
|
|
return nil, err
|
|
}
|
|
return b, nil
|
|
}
|
|
|
|
// WriteRetUnlink writes a RET_UNLINK body
|
|
func WriteRetUnlink(w io.Writer, b *RetUnlinkBody) error {
|
|
return binary.Write(w, binary.BigEndian, b)
|
|
}
|
|
|
|
// --- High-level message builders ---
|
|
|
|
// BuildDevlistReply builds a complete OP_REP_DEVLIST response
|
|
func BuildDevlistReply(devices []DeviceDescriptor, interfaces [][]InterfaceDescriptor) ([]byte, error) {
|
|
buf := &bytes.Buffer{}
|
|
|
|
// Header
|
|
if err := WriteOpHeader(buf, OpRepDevlist, 0); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Number of devices
|
|
if err := binary.Write(buf, binary.BigEndian, uint32(len(devices))); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Each device + its interfaces
|
|
for i, dev := range devices {
|
|
if err := WriteDeviceDescriptor(buf, &dev); err != nil {
|
|
return nil, err
|
|
}
|
|
if i < len(interfaces) {
|
|
for _, iface := range interfaces[i] {
|
|
if err := WriteInterfaceDescriptor(buf, &iface); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
// BuildImportReply builds an OP_REP_IMPORT response
|
|
func BuildImportReply(status uint32, dev *DeviceDescriptor) ([]byte, error) {
|
|
buf := &bytes.Buffer{}
|
|
|
|
if err := WriteOpHeader(buf, OpRepImport, status); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if status == 0 && dev != nil {
|
|
if err := WriteDeviceDescriptor(buf, dev); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
// BuildRetSubmit builds a RET_SUBMIT message.
|
|
// actualLength must be set for BOTH directions: it reports how many bytes
|
|
// were actually transferred. For OUT transfers the kernel driver checks this
|
|
// (e.g. UVC probe control expects actualLength == 26).
|
|
func BuildRetSubmit(seqNum, devID, direction, endpoint uint32, status int32, actualLength uint32, data []byte) ([]byte, error) {
|
|
buf := &bytes.Buffer{}
|
|
|
|
hdr := &URBHeader{
|
|
Command: RetSubmit,
|
|
SeqNum: seqNum,
|
|
DevID: devID,
|
|
Direction: direction,
|
|
Endpoint: endpoint,
|
|
}
|
|
if err := WriteURBHeader(buf, hdr); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
body := &RetSubmitBody{
|
|
Status: status,
|
|
ActualLength: actualLength,
|
|
NumberOfPackets: 0xFFFFFFFF,
|
|
}
|
|
if err := WriteRetSubmit(buf, body); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Transfer buffer for IN direction
|
|
if direction == DirIn && len(data) > 0 {
|
|
buf.Write(data)
|
|
}
|
|
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
// BuildRetSubmitISO builds a RET_SUBMIT message for isochronous transfers.
|
|
// The transfer data must already be packed (compact, no gaps).
|
|
func BuildRetSubmitISO(seqNum, devID, direction, endpoint uint32, status int32,
|
|
actualLength uint32, packedData []byte, startFrame uint32, numPackets int32, errorCount int32,
|
|
isoDescs []ISOPacketDescriptor) ([]byte, error) {
|
|
|
|
buf := &bytes.Buffer{}
|
|
|
|
hdr := &URBHeader{
|
|
Command: RetSubmit,
|
|
SeqNum: seqNum,
|
|
DevID: devID,
|
|
Direction: direction,
|
|
Endpoint: endpoint,
|
|
}
|
|
if err := WriteURBHeader(buf, hdr); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
body := &RetSubmitBody{
|
|
Status: status,
|
|
ActualLength: actualLength,
|
|
StartFrame: startFrame,
|
|
NumberOfPackets: uint32(numPackets),
|
|
ErrorCount: uint32(errorCount),
|
|
}
|
|
if err := WriteRetSubmit(buf, body); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Transfer buffer (packed) for IN direction
|
|
if direction == DirIn && len(packedData) > 0 {
|
|
buf.Write(packedData)
|
|
}
|
|
|
|
// ISO packet descriptors
|
|
for _, desc := range isoDescs {
|
|
if err := binary.Write(buf, binary.BigEndian, &desc); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
// BuildRetUnlink builds a RET_UNLINK message
|
|
func BuildRetUnlink(seqNum, devID uint32, status int32) ([]byte, error) {
|
|
buf := &bytes.Buffer{}
|
|
|
|
hdr := &URBHeader{
|
|
Command: RetUnlink,
|
|
SeqNum: seqNum,
|
|
DevID: devID,
|
|
Direction: 0,
|
|
Endpoint: 0,
|
|
}
|
|
if err := WriteURBHeader(buf, hdr); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
body := &RetUnlinkBody{Status: status}
|
|
if err := WriteRetUnlink(buf, body); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
// SetBusID sets a bus ID string in a fixed-size byte array
|
|
func SetBusID(arr *[32]byte, busID string) {
|
|
copy(arr[:], busID)
|
|
}
|
|
|
|
// SetPath sets a path string in a fixed-size byte array
|
|
func SetPath(arr *[256]byte, path string) {
|
|
copy(arr[:], path)
|
|
}
|
|
|
|
// GetBusID extracts a bus ID string from a fixed-size byte array
|
|
func GetBusID(arr [32]byte) string {
|
|
n := bytes.IndexByte(arr[:], 0)
|
|
if n < 0 {
|
|
n = 32
|
|
}
|
|
return string(arr[:n])
|
|
}
|
|
|
|
// GetPath extracts a path string from a fixed-size byte array
|
|
func GetPath(arr [256]byte) string {
|
|
n := bytes.IndexByte(arr[:], 0)
|
|
if n < 0 {
|
|
n = 256
|
|
}
|
|
return string(arr[:n])
|
|
}
|
|
|
|
// SpeedString returns a human-readable speed name
|
|
func SpeedString(speed uint32) string {
|
|
switch speed {
|
|
case SpeedLow:
|
|
return "1.5 Mbps (Low)"
|
|
case SpeedFull:
|
|
return "12 Mbps (Full)"
|
|
case SpeedHigh:
|
|
return "480 Mbps (High)"
|
|
case SpeedSuper:
|
|
return "5 Gbps (Super)"
|
|
case SpeedSuperPlus:
|
|
return "10 Gbps (Super+)"
|
|
default:
|
|
return fmt.Sprintf("Unknown (%d)", speed)
|
|
}
|
|
}
|