Documentation
¶
Overview ¶
Package domain defines the pure value objects, entities, events, and sentinel errors shared by the usbip-go library. No I/O, no goroutines, no third-party imports — only stdlib.
Index ¶
- Constants
- Variables
- type BusID
- type Device
- type DeviceBoundEvent
- type DeviceID
- type DeviceUnboundEvent
- type Event
- type EventKind
- type Interface
- type Port
- type PortAttachedEvent
- type PortDetachedEvent
- type PortErroredEvent
- type PortID
- type PortReconnectExhaustedEvent
- type RemoteEndpoint
- type Session
- type SessionEndedEvent
- type SessionID
- type SessionStartedEvent
- type Speed
- type Status
- type USBClass
- type USBProtocol
- type USBSubclass
Constants ¶
const ( DefaultPort uint16 = 3240 DefaultEndpoint = "0.0.0.0:3240" // ProtocolVersion is the USBIP protocol version (matches kernel // include/uapi/linux/usbip.h). USBIP 1.1.1. ProtocolVersion uint16 = 0x0111 // BusIDSize is the wire-format busid field length. BusIDSize = 32 // SysPathSize is the wire-format sysfs-path field length. SysPathSize = 256 // DefaultDialTimeout is the recommended dial budget for callers that want // an explicit bound. The transport zero value deliberately uses net.Dialer's // platform default and does not apply this constant automatically. DefaultDialTimeout = 10 * time.Second // DefaultHandshakeTimeout is the legacy recommended handshake budget. // // Deprecated: use DefaultExporterHandshakeTimeout for the runtime default or // configure an explicit timeout for the relevant operation. DefaultHandshakeTimeout = 5 * time.Second // DefaultExporterHandshakeTimeout is the Exporter's USB/IP OP handshake bound. DefaultExporterHandshakeTimeout = 10 * time.Second // DefaultShutdownTimeout is the CLI daemon's default graceful-shutdown budget. // Library callers control shutdown with their context or explicit options. DefaultShutdownTimeout = 30 * time.Second )
DefaultPort is the conventional USB/IP port. IANA registered TCP/3240 for "Trio Motion Control Port" in 2002; USB/IP's use of the same port is a de-facto convention predating most implementations, and changing it would break interop with upstream usbip-utils, usbipd-win, and every other implementation.
Variables ¶
var ( // ErrDeviceNotFound indicates the requested busid is not present. ErrDeviceNotFound = errors.New("device not found") // ErrDeviceAlreadyBound indicates the device is already bound to // usbip-host and cannot be bound again. ErrDeviceAlreadyBound = errors.New("device already bound") // ErrDeviceNotBound indicates the device is not currently bound // to usbip-host. ErrDeviceNotBound = errors.New("device not bound") // ErrPortInUse indicates a local vhci port is already attached. ErrPortInUse = errors.New("port in use") // ErrNoFreePort indicates no vhci port is available for attach. ErrNoFreePort = errors.New("no free vhci port") // ErrProtocolMismatch indicates the peer reported a different // USBIP protocol version. ErrProtocolMismatch = errors.New("usbip protocol version mismatch") // ErrProtocolError indicates the peer replied with a protocol // error status code (OP_REP_*.status != 0 on a handshake frame). ErrProtocolError = errors.New("usbip protocol error reported by peer") // ErrAlreadyRunning indicates another exporter instance is holding // the shared PID lock. ErrAlreadyRunning = errors.New("another instance is already running") // ErrAlreadyShutdown indicates the exporter has been shut down and // cannot accept further requests. ErrAlreadyShutdown = errors.New("exporter already shut down") // ErrBusIDInvalid indicates a bus id failed validation. ErrBusIDInvalid = errors.New("invalid bus id") // ErrPermission indicates the caller lacks the privileges needed // (typically CAP_SYS_ADMIN or root). ErrPermission = errors.New("operation requires elevated privileges") // ErrKernelModuleMissing indicates a required kernel module // (usbip-core/usbip-host/vhci-hcd) is not loaded. ErrKernelModuleMissing = errors.New("required kernel module not loaded") // ErrAttachInProgress indicates Attach is already running for the // same (remote, busid) pair. Concurrent Attach calls race the // fd-passing handoff and the handle-map insert, so the deduper // rejects the second caller with this sentinel (importer-lifecycle OpenSpec). // Hosted on pkg/domain so the public facade can re-export it // alongside the other spec-listed sentinels. ErrAttachInProgress = errors.New("attach already in progress for this endpoint") // ErrUnsupportedDevice indicates the device cannot be exported via // usbip — typically a USB hub (bDeviceClass=0x09) or the vhci-hcd // loopback device. Distinct from ErrDeviceNotFound (device exists) // and ErrBusIDInvalid (id format is valid). Surfaced BEFORE any // destructive sysfs write so detaching a hub's drivers does not // cascade-disconnect downstream devices. ErrUnsupportedDevice = errors.New("device not supported for usbip export") // device exists but is currently unusable for import (e.g. // stub-side internal error, ST_DEV_ERR=3 from upstream // usbip_common.h). Distinct from ErrDeviceAlreadyBound (busy/ // already attached, ST_DEV_BUSY=2) and ErrDeviceNotFound // (ST_NODEV=4). ErrDeviceUnavailable = errors.New("remote device unavailable") )
Sentinel errors returned by pkg/domain and by internal packages that surface domain-level conditions. Consumers match via errors.Is.
Message text is a stable part of the public API and MUST NOT be changed without a breaking-change bump. List ordering matches spec domain-model OpenSpec followed by the three additions in wire-protocol OpenSpec.
Functions ¶
This section is empty.
Types ¶
type BusID ¶
type BusID string
BusID is the stable USB topology identifier (e.g. "1-1.2").
On-wire max size is 32 bytes (BusIDSize); we enforce length < BusIDSize to leave room for a trailing NUL byte when serialized.
func ParseBusID ¶
ParseBusID validates s and returns a BusID.
Rejects: empty strings, all-whitespace strings, strings with length >= BusIDSize, strings containing NUL bytes, strings with leading or trailing whitespace, and strings that do not match the Linux USB topology pattern ^[0-9]+-[0-9]+(\.[0-9]+)*$.
func ValidateWireBusID ¶
ValidateWireBusID applies the wire-receive acceptance check: the field must be non-empty, shorter than the 32-byte field, and every byte must fall inside the sysfs-basename charset [A-Za-z0-9._-]. The charset is narrower than "printable ASCII" on purpose — peer busids flow directly into path.Join when the exporter opens the per-device sysfs attribute files, so any byte that could escape a basename (notably '/', but also whitespace and control bytes) is a traversal hazard. The charset still covers every real-world shape, including the vudc name "usbip-vudc.0"; user-facing entry points continue to route through ParseBusID's stricter topology check.
type Device ¶
type Device struct {
// Path is the sysfs path, meaningful for local devices only. It is
// the empty string for Devices decoded from a remote OP_REP_DEVLIST.
Path string
BusID BusID
BusNum uint16
DevNum uint16
Speed Speed
// VendorID is the USB spec idVendor.
VendorID uint16
// ProductID is the USB spec idProduct.
ProductID uint16
// BcdDevice is the USB spec bcdDevice release number. The USB-spec
// name is retained because it refers to a specific BCD-encoded field
// on the wire and is not a mere prefix convention.
BcdDevice uint16
Class USBClass
Subclass USBSubclass
Protocol USBProtocol
ConfigValue uint8
NumConfigs uint8
NumInterfaces uint8
// Manufacturer is the device-supplied iManufacturer string descriptor
// resolved from the kernel via /sys/bus/usb/devices/<busid>/manufacturer.
// Empty when the device descriptor's iManufacturer index is 0 or the
// device does not implement string descriptors. Meaningful for local
// devices only — OP_REP_DEVLIST does not carry these strings on the wire.
Manufacturer string
// Product is the device-supplied iProduct string descriptor (the human
// product name, e.g. "AX88179 Gigabit Ethernet"). Distinct from
// ProductID which is the numeric idProduct. Empty when not populated.
// Meaningful for local devices only.
Product string
// Interfaces is an owned, read-only slice. Consumers MUST NOT mutate
// the returned slice; future versions may copy-on-return if mutation
// causes bugs.
Interfaces []Interface
}
Device represents a USB device, either local (discovered via sysfs) or remote (decoded from an OP_REP_DEVLIST response). Fields follow Go naming conventions (noun-adjective) rather than USB spec names where the spec uses C-style prefixes.
type DeviceBoundEvent ¶
DeviceBoundEvent is emitted when a local device becomes exportable (bound to usbip-host).
func (DeviceBoundEvent) EventKind ¶
func (DeviceBoundEvent) EventKind() EventKind
EventKind implements Event.
type DeviceID ¶
type DeviceID uint32
DeviceID encodes (busnum << 16) | devnum, used in wire OP_REP_DEVLIST.
type DeviceUnboundEvent ¶
DeviceUnboundEvent is emitted when a local device is unbound from usbip-host and returned to its original driver.
func (DeviceUnboundEvent) EventKind ¶
func (DeviceUnboundEvent) EventKind() EventKind
EventKind implements Event.
type Event ¶
type Event interface {
EventKind() EventKind
}
Event is the closed polymorphic union of domain events. Each concrete event type has a fixed EventKind() returning its discriminator.
type EventKind ¶
type EventKind uint8
EventKind tags the concrete type of a domain event. Values are used as the JSON "kind" discriminator in json-contracts OpenSpec event streams.
type Interface ¶
type Interface struct {
Class USBClass
Subclass USBSubclass
Protocol USBProtocol
// Alt is the bAlternateSetting. It is meaningful ONLY for Devices
// constructed from local sysfs (e.g. ExporterKernel.ListLocalDevices).
//
// OP_REP_DEVLIST encodes each interface as 4 bytes — class, subclass,
// protocol, padding — with no bAlternateSetting on the wire. For
// remotely-listed Devices, Alt is always zero and MUST NOT be
// interpreted as meaningful.
Alt uint8
}
Interface describes a USB interface (bInterfaceClass/bInterfaceSubClass /bInterfaceProtocol triple).
Consumers MUST NOT mutate Device.Interfaces; treat it as read-only.
type Port ¶
type Port struct {
// ID is the numeric vhci port identifier.
ID PortID
// Status is the port state (available/used/error/...).
Status Status
// Speed is the negotiated USB speed.
Speed Speed
// DeviceID encodes (busnum << 16) | devnum of the remote device.
DeviceID DeviceID
// Remote is the peer address serving this port. It is zero when the
// attachment is known only from kernel state because VHCI does not retain
// exporter endpoint metadata.
Remote RemoteEndpoint
// BusID is the remote busid as reported by the exporter. It is empty when
// the attachment is known only from kernel state.
BusID BusID
// LocalBusID is the local representation of the busid if one exists;
// empty for ports without a local sysfs entry.
LocalBusID BusID
}
Port describes one vhci kernel status row. StatusNull and StatusAvailable rows represent free capacity. StatusNotAssigned represents a claimed vdev that has not received its local USB address yet.
type PortAttachedEvent ¶
PortAttachedEvent is emitted when a remote device is successfully attached.
func (PortAttachedEvent) EventKind ¶
func (PortAttachedEvent) EventKind() EventKind
EventKind implements Event.
type PortDetachedEvent ¶
PortDetachedEvent is emitted when a previously-attached port is released. Reason is a free-form human-readable explanation.
func (PortDetachedEvent) EventKind ¶
func (PortDetachedEvent) EventKind() EventKind
EventKind implements Event.
type PortErroredEvent ¶
PortErroredEvent is emitted when the vhci port transitions to the error state. Err is the error message captured at the transition.
func (PortErroredEvent) EventKind ¶
func (PortErroredEvent) EventKind() EventKind
EventKind implements Event.
type PortReconnectExhaustedEvent ¶
PortReconnectExhaustedEvent is emitted by the importer's reconnect watcher when MaxAttempts has been reached without a successful reattach. Port is a snapshot of the last successful Attach (the kernel slot is already gone at emission time, so this captures what was true while the port was viable). Attempts is the number of reconnect attempts actually made (not MaxAttempts). LastError is the stringified final attempt error; the domain layer does not carry Go error values across the JSON boundary.
LastError is diagnostic free-form text. Wrapping by the importer and kernel adapter typically embeds the peer endpoint, the BusID, and absolute sysfs paths. Downstream JSON consumers MUST treat this field as untrusted display text — do not parse it for control flow, and consider scrubbing it before forwarding to a less-privileged audience that should not see operator-level diagnostic strings.
func (PortReconnectExhaustedEvent) EventKind ¶
func (PortReconnectExhaustedEvent) EventKind() EventKind
EventKind implements Event.
type RemoteEndpoint ¶
RemoteEndpoint identifies a USB/IP peer by host and port.
Port zero is a sentinel meaning "use DefaultPort". The library never attempts to dial port 0; callers either construct a RemoteEndpoint with an explicit port or leave Port at its zero value to accept the default. Any library method that consumes a RemoteEndpoint calls NormalizePort internally, so the literal RemoteEndpoint{Host: "h"} behaves as "h:3240".
func ParseRemote ¶
func ParseRemote(s string) (RemoteEndpoint, error)
ParseRemote accepts "host", "host:port", "[v6]:port", bare "v6" (multi-colon), or bracketed "[v6]" forms. Port defaults to DefaultPort when omitted. Port 0 is rejected as a sentinel value. Host strings are validated: no whitespace, no control characters, multi-colon forms must parse as a valid IP literal, and hostnames must satisfy RFC 1034 + RFC 1123 label rules.
func (RemoteEndpoint) NormalizePort ¶
func (r RemoteEndpoint) NormalizePort() RemoteEndpoint
NormalizePort returns a copy of r with Port set to DefaultPort when the original Port was zero. The input is unchanged.
func (RemoteEndpoint) String ¶
func (r RemoteEndpoint) String() string
String returns "host:port" using DefaultPort when r.Port is zero. IPv6 hosts are bracketed via net.JoinHostPort.
func (RemoteEndpoint) Validate ¶
func (r RemoteEndpoint) Validate() error
Validate applies the same host-acceptance rules as ParseRemote to a programmatically constructed endpoint. Callers that accept a RemoteEndpoint from outside their own code (public API boundaries, RPC payloads, config deserialisation) invoke Validate before dialing; this keeps the dialer path pure and makes empty-Host input a first-class error instead of an accidental loopback dial.
type Session ¶
type Session struct {
// ID is the unique session identifier (UUIDv7).
ID SessionID
// RemoteAddr is the peer address+port as observed by the daemon.
RemoteAddr netip.AddrPort
// BusID is the exported device's busid for this session.
BusID BusID
// StartedAt is the wall-clock time at which the session handshake completed.
StartedAt time.Time
// BytesIn counts bytes received from the peer.
BytesIn uint64
// BytesOut counts bytes transmitted to the peer.
BytesOut uint64
}
Session describes a single client connection from the daemon's view.
type SessionEndedEvent ¶
SessionEndedEvent is emitted when a Session closes, for any reason.
func (SessionEndedEvent) EventKind ¶
func (SessionEndedEvent) EventKind() EventKind
EventKind implements Event.
type SessionID ¶
type SessionID [16]byte
SessionID is a 16-byte UUIDv7 identifying a single client connection. The UUIDv7 layout (RFC 9562 §5.7) embeds a millisecond Unix timestamp in the high 48 bits, giving sessions a natural chronological ordering.
func NewSessionID ¶
NewSessionID returns a freshly-generated UUIDv7. It returns an error when the underlying crypto/rand source fails.
Layout per RFC 9562 §5.7:
bytes 0..5 unix_ts_ms (48 bits, big-endian) byte 6 version 7 in high nibble + rand_a[0..3] in low nibble byte 7 rand_a[4..11] byte 8 variant `10` in top 2 bits + rand_b[0..5] bytes 9..15 rand_b[6..69]
Implemented inline against crypto/rand instead of a third-party UUID package so pkg/domain stays a pure-stdlib value-object surface (the invariant the Bazel-backed lint suite protects).
func (SessionID) String ¶
String returns the canonical 36-character hyphenated UUID form (8-4-4-4-12 lowercase hex), matching the output of every RFC 9562-conformant UUID library. The five hex segments are emitted then the four hyphens overwrite the segment boundaries in a separate pass — wsl_v5 dislikes the alternating call / assign / call shape that the inline form would produce.
type SessionStartedEvent ¶
SessionStartedEvent is emitted when a client completes the USBIP handshake and is assigned a Session.
func (SessionStartedEvent) EventKind ¶
func (SessionStartedEvent) EventKind() EventKind
EventKind implements Event.
type Speed ¶
type Speed uint32
Speed is a USB speed enum matching kernel enum usb_device_speed values. Sysfs reports Mbps strings ("5000"), not these integers; the kernel adapter translates via ReadSpeedAttr before populating this field.
const ( SpeedUnknown Speed = 0 SpeedLow Speed = 1 // 1.5 Mbit/s SpeedFull Speed = 2 // 12 Mbit/s SpeedHigh Speed = 3 // 480 Mbit/s SpeedWireless Speed = 4 SpeedSuper Speed = 5 // 5 Gbit/s SpeedSuperPlus Speed = 6 // 10 or 20 Gbit/s (USB 3.1 Gen 2, USB 3.2 Gen 2x2) )
USB device speeds, numeric values match the kernel's usb_device_speed.
func (Speed) IsKnown ¶
IsKnown reports whether s falls inside the finite enum declared above. Wire decoders call IsKnown after reading the 4-byte field so a peer emitting a value the kernel does not define cannot round-trip as a mystery domain.Speed that downstream consumers (structured logs, CLI rendering, event delivery) would silently carry.
type Status ¶
type Status uint32
Status is a vhci port state matching kernel vdev_status values.
type USBClass ¶
type USBClass uint8
USBClass is a USB device/interface class code (bDeviceClass/bInterfaceClass).
type USBProtocol ¶
type USBProtocol uint8
USBProtocol is a USB device/interface protocol code. Protocol semantics depend on the owning USBClass + USBSubclass.
func (USBProtocol) String ¶
func (p USBProtocol) String() string
String returns a hex-formatted protocol code (Protocol meaning is only defined within its owning class+subclass).
type USBSubclass ¶
type USBSubclass uint8
USBSubclass is a USB device/interface subclass code. Subclass semantics depend on the owning USBClass; see usb.org class specifications.
func (USBSubclass) String ¶
func (s USBSubclass) String() string
String returns a hex-formatted subclass code (Subclass meaning is only defined within its owning class; the committed data is class-indexed).