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) } }