usb-server/internal/usbip/protocol.go

375 lines
8.4 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
func BuildRetSubmit(seqNum, devID, direction, endpoint uint32, status int32, 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
}
actualLen := uint32(0)
if direction == DirIn && data != nil {
actualLen = uint32(len(data))
}
body := &RetSubmitBody{
Status: status,
ActualLength: actualLen,
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
}
// 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)
}
}