Documentation
¶
Overview ¶
Package wire implements the USB/IP OP-level protocol codec: opcodes, header layout, device descriptor (312 bytes), OP_REQ_DEVLIST / OP_REP_DEVLIST, and OP_REQ_IMPORT / OP_REP_IMPORT. Encoding is byte-faithful to the upstream kernel implementation.
Index ¶
- Constants
- func DecodeOpReqImport(r io.Reader) (domain.BusID, error)
- func DecodeOpReqImportBody(r io.Reader) (domain.BusID, error)
- func EncodeDevice(w io.Writer, d domain.Device) error
- func EncodeHeader(op OpCode, status uint32) []byte
- func EncodeOpRepDevlist(w io.Writer, devices []domain.Device) error
- func EncodeOpRepImport(w io.Writer, dev domain.Device) error
- func EncodeOpRepImportError(w io.Writer, status uint32) error
- func EncodeOpReqDevlist() []byte
- func EncodeOpReqImport(w io.Writer, busID domain.BusID) error
- func ReadPaddedString(r io.Reader, size int) (string, bool, error)
- func WritePaddedString(w io.Writer, s string, size int) error
- type Codec
- func (*Codec) DecodeHeader(r io.Reader) (uint16, OpCode, uint32, error)
- func (c *Codec) DecodeOpRepDevlist(r io.Reader) ([]domain.Device, error)
- func (c *Codec) DecodeOpRepImport(r io.Reader) (domain.Device, error)
- func (*Codec) DecodeOpReqImport(r io.Reader) (domain.BusID, error)
- func (*Codec) DecodeOpReqImportBody(r io.Reader) (domain.BusID, error)
- func (*Codec) EncodeHeader(op OpCode, status uint32) []byte
- func (*Codec) EncodeOpRepDevlist(w io.Writer, devices []domain.Device) error
- func (*Codec) EncodeOpRepImport(w io.Writer, dev domain.Device) error
- func (*Codec) EncodeOpRepImportError(w io.Writer, status uint32) error
- func (*Codec) EncodeOpReqDevlist() []byte
- func (*Codec) EncodeOpReqImport(w io.Writer, busID domain.BusID) error
- type DecodeFlags
- type OpCode
- type Option
- type PaddedStringTruncation
Constants ¶
const ( ImportStatusNA = protocol.ImportStatusNA ImportStatusDevBusy = protocol.ImportStatusDevBusy ImportStatusDevErr = protocol.ImportStatusDevErr ImportStatusNoDev = protocol.ImportStatusNoDev )
OP_REP_IMPORT status codes from upstream tools/usb/usbip/libsrc/ usbip_common.h. Exported so app-layer code can encode the appropriate error on the exporter side without redeclaring the upstream protocol constants.
ST_OK = 0 // success (encoded as the absence of an error) ST_NA = 1 // device not exported / unknown ST_DEV_BUSY = 2 // already in use by another importer ST_DEV_ERR = 3 // stub-side internal error ST_NODEV = 4 // no such device on remote
const ( // OpReqDevlist is the client's request to list exportable devices. OpReqDevlist = protocol.OpReqDevlist // OpRepDevlist is the server's response to OpReqDevlist. OpRepDevlist = protocol.OpRepDevlist // OpReqImport is the client's request to import (attach) a device. OpReqImport = protocol.OpReqImport // OpRepImport is the server's response to OpReqImport. OpRepImport = protocol.OpRepImport )
OpCode constants as defined in wire-protocol OpenSpec.
const DeviceWireSize = 312
DeviceWireSize is the on-wire width of the 312-byte device descriptor layout (wire-protocol OpenSpec). This is the OP_REP_DEVLIST and OP_REP_IMPORT device-body size.
const MaxDevlistDevices = 1024
MaxDevlistDevices caps the device count honoured by DecodeOpRepDevlist. A peer that declares more devices than this has either a pathological enumeration or is mounting a denial-of-service attack against the importer's allocator. 1024 is already well past the plausible ceiling for any real USB hub topology.
Variables ¶
This section is empty.
Functions ¶
func DecodeOpReqImport ¶
DecodeOpReqImport reads a full OP_REQ_IMPORT request (header + body) and returns the requested busid. Used by callers that own the read of the entire request from raw bytes.
Daemons whose dispatcher already consumed the header MUST call DecodeOpReqImportBody instead — calling this function would re-read 8 bytes from the busid region and surface them as a (bogus) version mismatch.
func DecodeOpReqImportBody ¶
DecodeOpReqImportBody reads ONLY the OP_REQ_IMPORT body (the 32-byte busid) from r. The 8-byte header must already have been consumed by the caller — this function does NOT re-read it.
Body-only entry point so the daemon's accept dispatcher (which already decoded the header to route the connection) does not double-read the header and mis-decode the busid as a header.
func EncodeDevice ¶
EncodeDevice serializes d into the 312-byte on-wire device descriptor format (wire-protocol OpenSpec) and writes it to w.
Returns ErrBusIDInvalid if d.BusID >= 32 bytes, ErrProtocolError if d.Path >= 256 bytes, and propagates underlying writer errors wrapped.
func EncodeHeader ¶
EncodeHeader returns the 8-byte OP header for op with the given status. The returned slice has length headerSize and is owned by the caller. Layout (big-endian):
u16 version (ProtocolVersion) u16 opcode u32 status
func EncodeOpRepDevlist ¶
EncodeOpRepDevlist writes an OP_REP_DEVLIST reply for the supplied devices (wire-protocol OpenSpec). Each device is serialized via EncodeDevice followed by its NumInterfaces four-byte interface descriptors.
When d.NumInterfaces does not match len(d.Interfaces) the declared count wins (matching upstream usbipd behavior): extra interfaces in the slice are dropped; missing entries are padded with zero bytes.
func EncodeOpRepImport ¶
EncodeOpRepImport writes a success OP_REP_IMPORT reply (status=0) with the device body (wire-protocol OpenSpec).
func EncodeOpRepImportError ¶
EncodeOpRepImportError writes an error OP_REP_IMPORT reply (status != 0, no device body) per wire-protocol OpenSpec. status MUST be one of the upstream ST_* codes (ST_NA=1, ST_DEV_BUSY=2, ST_DEV_ERR=3, ST_NODEV=4). A zero status would let the peer decode a body that the wire frame does not carry; an unknown status would surface as ErrProtocolError on the importer side and obscure the rejection. Both are rejected at encode.
func EncodeOpReqDevlist ¶
func EncodeOpReqDevlist() []byte
EncodeOpReqDevlist returns the 8-byte OP_REQ_DEVLIST request (pure header). The request carries no body (wire-protocol OpenSpec).
func EncodeOpReqImport ¶
EncodeOpReqImport writes an OP_REQ_IMPORT request for the supplied busid: 8-byte header + 32-byte NUL-padded busid (wire-protocol OpenSpec).
func ReadPaddedString ¶
ReadPaddedString reads exactly size bytes from r, truncates at the first NUL byte, and returns the decoded string together with a flag indicating whether the buffer was non-NUL-terminated (i.e. used the full size because no NUL was found).
The caller decides what to do with truncated == true; this helper does not log. Non-terminated frames are permissive on read (spec wire-protocol OpenSpec: "Non-NUL-terminated padded string → truncated at first non-printable or end of buffer; no error; logged as slog.Warn" — the logging is done by the Codec method, which has an injected *slog.Logger; see Codec.DecodeOpRepDevlist and related methods).
On short read returns io.ErrUnexpectedEOF wrapped with size context.
func WritePaddedString ¶
WritePaddedString writes s NUL-padded to exactly size bytes into w.
The USBIP wire format encodes path and busid fields as fixed-width byte arrays, where a trailing NUL indicates end-of-string. Strings must therefore be strictly shorter than size (len(s) <= size-1) to leave room for at least one NUL terminator. On overflow the returned error depends on the field:
- size == BusIDSize → ErrBusIDInvalid (busid is the public programmer-visible field; misuse should match the public sentinel)
- otherwise → ErrProtocolError (path and other fixed-width fields are protocol-level; misuse is an internal protocol error)
Types ¶
type Codec ¶
type Codec struct {
// contains filtered or unexported fields
}
Codec is the wire-level USBIP protocol codec. All methods forward to the package-level encode/decode helpers. Per architecture-layering OpenSpec this type implements the app.ProtocolCodec interface; the compile-time assertion above keeps the adapter honest without making app import this package.
Codec carries a *slog.Logger so permissive-read signals surfaced by the package-level helpers (e.g. trailing bytes after OP_REP_DEVLIST, wire-protocol OpenSpec) can be logged to a caller-controlled sink. Zero-value Codec{} is usable; its logger is a no-op handler so unit tests that construct Codec{} see no output. Inject a real logger via the WithLogger option.
func NewCodec ¶
NewCodec constructs a Codec with no-op logging. Apply options to customise (currently only WithLogger).
func (*Codec) DecodeHeader ¶
DecodeHeader forwards to the package-level DecodeHeader.
func (*Codec) DecodeOpRepDevlist ¶
DecodeOpRepDevlist calls the package-level decoder, logs any advisory signals surfaced in DecodeFlags (trailing bytes after the declared frame, padded-string fields that reached end-of-field without NUL per wire-protocol OpenSpec), and returns the decoded devices without the flags — the app-facing interface stays narrow.
func (*Codec) DecodeOpRepImport ¶
DecodeOpRepImport calls the package-level decoder, logs any padded-string truncation signals surfaced in DecodeFlags, and returns the decoded device without the flags.
func (*Codec) DecodeOpReqImport ¶
DecodeOpReqImport forwards to the package-level DecodeOpReqImport.
func (*Codec) DecodeOpReqImportBody ¶
DecodeOpReqImportBody forwards to the package-level body-only decoder. Daemon dispatchers that already consumed the header use this to read just the busid without re-reading the 8 header bytes.
func (*Codec) EncodeHeader ¶
EncodeHeader forwards to the package-level EncodeHeader.
func (*Codec) EncodeOpRepDevlist ¶
EncodeOpRepDevlist forwards to the package-level EncodeOpRepDevlist.
func (*Codec) EncodeOpRepImport ¶
EncodeOpRepImport forwards to the package-level EncodeOpRepImport.
func (*Codec) EncodeOpRepImportError ¶
EncodeOpRepImportError forwards to the package-level EncodeOpRepImportError. Used by the exporter to reject an import request with an upstream-defined ST_* status code (no body).
func (*Codec) EncodeOpReqDevlist ¶
EncodeOpReqDevlist forwards to the package-level EncodeOpReqDevlist.
type DecodeFlags ¶
type DecodeFlags struct {
// TruncatedPaddedStrings records every fixed-width string field
// whose bytes reached the end of the field without a NUL
// terminator. Each entry names the field and the 0-based device
// position inside the reply (0 for both single-device replies and
// the first device of a devlist).
TruncatedPaddedStrings []PaddedStringTruncation
// TrailingBytes is true when the decoder observed bytes after the
// declared frame boundary. Currently set only by
// DecodeOpRepDevlist.
TrailingBytes bool
}
DecodeFlags carries advisory signals produced by a decode call that the wire-protocol OpenSpec permissive-read rule keeps out of the error channel. Codec methods consume the flags and emit slog.Warn records; direct callers of the package-level decoders can inspect the struct or ignore it. An empty DecodeFlags represents a clean decode.
func DecodeDevice ¶
DecodeDevice reads 312 bytes from r and returns the decoded Device plus the advisory DecodeFlags that record any padded-string truncation the wire-protocol OpenSpec permissive-read rule keeps out of the error channel. Short reads surface as io.ErrUnexpectedEOF wrapped with field context. Oversized busnum/devnum u32 fields (> uint16 max) and unknown Speed values surface as ErrProtocolError.
func DecodeOpRepDevlist ¶
DecodeOpRepDevlist decodes an OP_REP_DEVLIST reply from r. It returns nil devices and zero DecodeFlags for a zero-device reply. TrailingBytes is true when bytes remain after the last device (wire-protocol OpenSpec "permissive on read"); the caller (typically the Codec) logs via its injected logger if desired. Errors follow wire-protocol OpenSpec error matrix:
- Truncated mid-device → io.ErrUnexpectedEOF wrapped with "truncated devlist at index N" where N is the count of devices successfully decoded so far.
- Truncated mid-interface / declared interface count exceeds remaining bytes → io.ErrUnexpectedEOF wrapped with "truncated interfaces".
func DecodeOpRepImport ¶
DecodeOpRepImport reads an OP_REP_IMPORT reply. Per wire-protocol OpenSpec the header's status field means "device unavailable / busy / not found" on this opcode — a domain-level rejection, not a wire framing fault. A non-zero status surfaces as domain.ErrDeviceNotFound so the Importer.Attach caller sees the canonical rejection sentinel (RANK 5). A zero status returns the decoded device body.
Because of that opcode-specific semantic, DecodeOpRepImport calls the internal decodeHeaderAllowStatus helper directly — DecodeHeader would convert a non-zero status into ErrProtocolError and hide the rejection behind a misleading classification.
type OpCode ¶
OpCode is the shared USB/IP handshake opcode type.
func DecodeHeader ¶
DecodeHeader reads an 8-byte OP header from r. Per wire-protocol OpenSpec error matrix:
- Clean EOF before any byte is read → io.EOF (unwrapped).
- Partial header → io.ErrUnexpectedEOF wrapped.
- Version != ProtocolVersion → ErrProtocolMismatch.
- Unknown opcode → ErrProtocolMismatch.
- Status != 0 → ErrProtocolError (all opcodes; see OP_REP_IMPORT exception below).
OP_REP_IMPORT is the narrow exception to the status-non-zero rule: the spec treats a non-zero OP_REP_IMPORT status as the peer saying "device unavailable / busy / not found" (a domain-level rejection), not a wire framing fault. DecodeOpRepImport calls decodeHeaderAllowStatus directly and classifies the status itself. That path never reaches DecodeHeader, so the unconditional status check here is safe.
The 4-tuple return is dictated by the spec-level codec surface: callers need both the raw version (for diagnostics) and the validated opcode + status.
type Option ¶
type Option func(*Codec)
Option configures a Codec.
func WithLogger ¶
WithLogger installs l as the Codec's logger. Passing nil selects the no-op handler.
type PaddedStringTruncation ¶
type PaddedStringTruncation struct {
// Field is a dotted identifier naming the truncated field
// (e.g. "device.path", "device.busid").
Field string
// DeviceIndex is the 0-based position inside the reply. Devlist
// decoders overwrite it with the in-slice index of each device;
// single-device decoders (DecodeOpRepImport, direct DecodeDevice)
// leave it at 0.
DeviceIndex int
}
PaddedStringTruncation identifies one truncated padded-string field in a decoded payload.