first commit
This commit is contained in:
@@ -0,0 +1,374 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user