wire

package
v1.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 18, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

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

View Source
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
View Source
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.

View Source
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.

View Source
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

func DecodeOpReqImport(r io.Reader) (domain.BusID, error)

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

func DecodeOpReqImportBody(r io.Reader) (domain.BusID, error)

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

func EncodeDevice(w io.Writer, d domain.Device) error

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

func EncodeHeader(op OpCode, status uint32) []byte

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

func EncodeOpRepDevlist(w io.Writer, devices []domain.Device) error

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

func EncodeOpRepImport(w io.Writer, dev domain.Device) error

EncodeOpRepImport writes a success OP_REP_IMPORT reply (status=0) with the device body (wire-protocol OpenSpec).

func EncodeOpRepImportError

func EncodeOpRepImportError(w io.Writer, status uint32) error

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

func EncodeOpReqImport(w io.Writer, busID domain.BusID) error

EncodeOpReqImport writes an OP_REQ_IMPORT request for the supplied busid: 8-byte header + 32-byte NUL-padded busid (wire-protocol OpenSpec).

func ReadPaddedString

func ReadPaddedString(r io.Reader, size int) (string, bool, error)

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

func WritePaddedString(w io.Writer, s string, size int) error

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

func NewCodec(opts ...Option) *Codec

NewCodec constructs a Codec with no-op logging. Apply options to customise (currently only WithLogger).

func (*Codec) DecodeHeader

func (*Codec) DecodeHeader(r io.Reader) (uint16, OpCode, uint32, error)

DecodeHeader forwards to the package-level DecodeHeader.

func (*Codec) DecodeOpRepDevlist

func (c *Codec) DecodeOpRepDevlist(r io.Reader) ([]domain.Device, error)

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

func (c *Codec) DecodeOpRepImport(r io.Reader) (domain.Device, error)

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

func (*Codec) DecodeOpReqImport(r io.Reader) (domain.BusID, error)

DecodeOpReqImport forwards to the package-level DecodeOpReqImport.

func (*Codec) DecodeOpReqImportBody

func (*Codec) DecodeOpReqImportBody(r io.Reader) (domain.BusID, error)

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

func (*Codec) EncodeHeader(op OpCode, status uint32) []byte

EncodeHeader forwards to the package-level EncodeHeader.

func (*Codec) EncodeOpRepDevlist

func (*Codec) EncodeOpRepDevlist(w io.Writer, devices []domain.Device) error

EncodeOpRepDevlist forwards to the package-level EncodeOpRepDevlist.

func (*Codec) EncodeOpRepImport

func (*Codec) EncodeOpRepImport(w io.Writer, dev domain.Device) error

EncodeOpRepImport forwards to the package-level EncodeOpRepImport.

func (*Codec) EncodeOpRepImportError

func (*Codec) EncodeOpRepImportError(w io.Writer, status uint32) error

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

func (*Codec) EncodeOpReqDevlist() []byte

EncodeOpReqDevlist forwards to the package-level EncodeOpReqDevlist.

func (*Codec) EncodeOpReqImport

func (*Codec) EncodeOpReqImport(w io.Writer, busID domain.BusID) error

EncodeOpReqImport forwards to the package-level EncodeOpReqImport.

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

func DecodeDevice(r io.Reader) (domain.Device, DecodeFlags, error)

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

func DecodeOpRepDevlist(r io.Reader) ([]domain.Device, DecodeFlags, error)

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

func DecodeOpRepImport(r io.Reader) (domain.Device, DecodeFlags, error)

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

type OpCode = protocol.OpCode

OpCode is the shared USB/IP handshake opcode type.

func DecodeHeader

func DecodeHeader(r io.Reader) (uint16, OpCode, uint32, error)

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

func WithLogger(l *slog.Logger) Option

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL