Skip to content

Commit 3a73604

Browse files
committed
USB transport
1 parent e2c3400 commit 3a73604

File tree

6 files changed

+679
-0
lines changed

6 files changed

+679
-0
lines changed

aggligator-util/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ tcp = ["tokio/net"]
1717
tls = ["rustls", "tokio-rustls"]
1818
rfcomm = ["bluer/rfcomm"]
1919
rfcomm-profile = ["bluer/rfcomm", "bluer/bluetoothd"]
20+
usb-host = ["upc/host", "rusb"]
21+
usb-device = ["upc/device", "usb-gadget"]
2022
websocket = ["tcp", "axum", "tungstenite", "tokio-tungstenite", "url"]
2123
cli = [
2224
"tcp",
@@ -34,6 +36,7 @@ cli = [
3436
"rustls-pemfile",
3537
"rustls/dangerous_configuration",
3638
"axum-server",
39+
"gethostname",
3740
]
3841
raw-speed-cli = ["cli"]
3942
speed = ["rand", "rand_xoshiro"]
@@ -72,6 +75,10 @@ tokio-tungstenite = { version = "0.20", features = [
7275
], optional = true }
7376
url = { version = "2", optional = true }
7477
axum-server = { version = "0.5", optional = true }
78+
upc = { version = "0.2.2", optional = true }
79+
usb-gadget = { version = "0.4", optional = true }
80+
rusb = { version = "0.9", optional = true }
81+
gethostname = { version = "0.4", optional = true }
7582

7683
[[bin]]
7784
name = "agg-speed"

aggligator-util/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ The following crate features are available:
2929
* `tcp` - TCP transport,
3030
* `rfcomm` - Bluetooth RFCOMM transport (Linux-only),
3131
* `rfcomm-profile` - Bluetooth RFCOMM transport using profiles for connecting (Linux-only),
32+
* `usb-host` - host-side USB transport,
33+
* `usb-device` - device-side USB transport,
3234
* `websocket` - WebSocket transport,
3335
* `monitor` — enables the text-based, interactive connection and link monitor,
3436
* `speed` — enables speed test functions,

aggligator-util/src/bin/agg-speed.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,21 @@ const DUMP_BUFFER: usize = 8192;
4747
const WEBSOCKET_PORT: u16 = 8080;
4848
const WEBSOCKET_PATH: &str = "/agg-speed";
4949

50+
#[cfg(any(feature = "usb-host", feature = "usb-device"))]
51+
mod usb {
52+
pub const VID: u16 = u16::MAX - 1;
53+
pub const PID: u16 = u16::MAX - 1;
54+
pub const MANUFACTURER: &str = env!("CARGO_PKG_NAME");
55+
pub const PRODUCT: &str = env!("CARGO_BIN_NAME");
56+
pub const CLASS: u8 = 255;
57+
pub const SUB_CLASS: u8 = 255;
58+
pub const PROTOCOL: u8 = 255;
59+
pub const INTERFACE_CLASS: u8 = 255;
60+
pub const INTERFACE_SUB_CLASS: u8 = 230;
61+
pub const INTERFACE_PROTOCOL: u8 = 231;
62+
pub const INTERFACE_NAME: &str = "speed test";
63+
}
64+
5065
#[cfg(feature = "rfcomm")]
5166
const RFCOMM_CHANNEL: u8 = 20;
5267
#[cfg(feature = "rfcomm-profile")]
@@ -196,6 +211,10 @@ pub struct ClientCli {
196211
#[cfg(feature = "rfcomm-profile")]
197212
#[arg(long)]
198213
rfcomm_profile: Option<bluer::Address>,
214+
/// USB device serial number (equals hostname of speed test device).
215+
#[cfg(feature = "usb-host")]
216+
#[arg(long)]
217+
usb: Option<String>,
199218
}
200219

201220
#[cfg(feature = "rfcomm")]
@@ -256,6 +275,30 @@ impl ClientCli {
256275
connector.add(rfcomm_profile_connector);
257276
}
258277

278+
#[cfg(feature = "usb-host")]
279+
if let Some(serial) = &self.usb {
280+
let filter_serial = serial.clone();
281+
let filter = move |dev: &aggligator_util::transport::usb::DeviceInfo,
282+
iface: &aggligator_util::transport::usb::InterfaceInfo| {
283+
dev.vendor_id == usb::VID
284+
&& dev.product_id == usb::PID
285+
&& dev.manufacturer == usb::MANUFACTURER
286+
&& dev.product == usb::PRODUCT
287+
&& dev.serial_number == filter_serial
288+
&& dev.class_code == usb::CLASS
289+
&& dev.sub_class_code == usb::SUB_CLASS
290+
&& dev.protocol_code == usb::PROTOCOL
291+
&& iface.class_code == usb::INTERFACE_CLASS
292+
&& iface.sub_class_code == usb::INTERFACE_SUB_CLASS
293+
&& iface.protocol_code == usb::INTERFACE_PROTOCOL
294+
&& iface.description == usb::INTERFACE_NAME
295+
};
296+
let usb_connector =
297+
aggligator_util::transport::usb::UsbConnector::new(filter).context("cannot initialize USB")?;
298+
targets.push(format!("USB {serial}"));
299+
connector.add(usb_connector);
300+
}
301+
259302
if !self.websocket.is_empty() {
260303
let websockets = self.websocket.iter().map(|url| {
261304
let mut url = url.clone();
@@ -422,6 +465,10 @@ pub struct ServerCli {
422465
#[cfg(feature = "rfcomm")]
423466
#[arg(long, default_value_t = RFCOMM_CHANNEL)]
424467
rfcomm: u8,
468+
/// Listen on USB device controller (UDC).
469+
#[cfg(feature = "usb-device")]
470+
#[arg(long)]
471+
usb: bool,
425472
/// WebSocket (HTTP) port to listen on.
426473
#[arg(long, default_value_t = WEBSOCKET_PORT)]
427474
websocket: u16,
@@ -479,6 +526,51 @@ impl ServerCli {
479526
Err(err) => eprintln!("Cannot listen on RFCOMM profile {RFCOMM_UUID}: {err}"),
480527
}
481528

529+
#[cfg(feature = "usb-device")]
530+
let _usb_reg = if self.usb {
531+
fn register_usb(
532+
serial: &str,
533+
) -> Result<(usb_gadget::RegGadget, upc::device::UpcFunction, std::ffi::OsString)> {
534+
let udc = usb_gadget::default_udc()?;
535+
let udc_name = udc.name().to_os_string();
536+
537+
let (upc, func_hnd) = upc::device::UpcFunction::new(
538+
upc::device::InterfaceId::new(upc::Class::new(
539+
usb::INTERFACE_CLASS,
540+
usb::INTERFACE_SUB_CLASS,
541+
usb::INTERFACE_PROTOCOL,
542+
))
543+
.with_name(usb::INTERFACE_NAME),
544+
);
545+
546+
let reg = usb_gadget::Gadget::new(
547+
usb_gadget::Class::new(usb::CLASS, usb::SUB_CLASS, usb::PROTOCOL),
548+
usb_gadget::Id::new(usb::VID, usb::PID),
549+
usb_gadget::Strings::new(usb::MANUFACTURER, usb::PRODUCT, serial),
550+
)
551+
.with_os_descriptor(usb_gadget::OsDescriptor::microsoft())
552+
.with_config(usb_gadget::Config::new("config").with_function(func_hnd))
553+
.bind(&udc)?;
554+
555+
Ok((reg, upc, udc_name))
556+
}
557+
558+
let serial = gethostname::gethostname().to_string_lossy().to_string();
559+
match register_usb(&serial) {
560+
Ok((usb_reg, upc, udc_name)) => {
561+
acceptor.add(aggligator_util::transport::usb::UsbAcceptor::new(upc, &udc_name));
562+
ports.push(format!("UDC {} ({serial})", udc_name.to_string_lossy()));
563+
Some(usb_reg)
564+
}
565+
Err(err) => {
566+
eprintln!("Cannot listen on USB: {err}");
567+
None
568+
}
569+
}
570+
} else {
571+
None
572+
};
573+
482574
let (wsa, router) = WebSocketAcceptor::new(WEBSOCKET_PATH);
483575
acceptor.add(wsa);
484576
ports.push(format!("WebSocket {}", self.websocket));

aggligator-util/src/bin/agg-tunnel.rs

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,21 @@ const TCP_PORT: u16 = 5800;
4343
const FLUSH_DELAY: Option<Duration> = Some(Duration::from_millis(10));
4444
const DUMP_BUFFER: usize = 8192;
4545

46+
#[cfg(any(feature = "usb-host", feature = "usb-device"))]
47+
mod usb {
48+
pub const VID: u16 = u16::MAX - 2;
49+
pub const PID: u16 = u16::MAX - 2;
50+
pub const MANUFACTURER: &str = env!("CARGO_PKG_NAME");
51+
pub const PRODUCT: &str = env!("CARGO_BIN_NAME");
52+
pub const CLASS: u8 = 255;
53+
pub const SUB_CLASS: u8 = 255;
54+
pub const PROTOCOL: u8 = 255;
55+
pub const INTERFACE_CLASS: u8 = 255;
56+
pub const INTERFACE_SUB_CLASS: u8 = 240;
57+
pub const INTERFACE_PROTOCOL: u8 = 1;
58+
pub const DEFAULT_INTERFACE_NAME: &str = "agg-tunnel";
59+
}
60+
4661
/// Forward TCP ports through a connection of aggregated links.
4762
///
4863
/// This uses Aggligator to combine multiple TCP links into one connection,
@@ -124,6 +139,16 @@ pub struct ClientCli {
124139
#[cfg(feature = "rfcomm")]
125140
#[arg(long)]
126141
rfcomm: Option<bluer::rfcomm::SocketAddr>,
142+
/// USB device serial number (equals hostname of speed test device).
143+
///
144+
/// Use - to match any device.
145+
#[cfg(feature = "usb-host")]
146+
#[arg(long)]
147+
usb: Option<String>,
148+
/// USB interface name.
149+
#[cfg(feature = "usb-host")]
150+
#[arg(long, default_value=usb::DEFAULT_INTERFACE_NAME)]
151+
usb_interface_name: String,
127152
}
128153

129154
impl ClientCli {
@@ -167,6 +192,46 @@ impl ClientCli {
167192
None => None,
168193
};
169194

195+
#[cfg(feature = "usb-host")]
196+
let usb_connector = {
197+
if let Some(serial) = &self.usb {
198+
targets.push(format!("USB {serial}"));
199+
}
200+
201+
let usb = self.usb.clone();
202+
move || match &usb {
203+
Some(serial) => {
204+
let filter_serial = serial.clone();
205+
let filter_interface_name = self.usb_interface_name.clone();
206+
let filter =
207+
move |dev: &aggligator_util::transport::usb::DeviceInfo,
208+
iface: &aggligator_util::transport::usb::InterfaceInfo| {
209+
dev.vendor_id == usb::VID
210+
&& dev.product_id == usb::PID
211+
&& dev.manufacturer == usb::MANUFACTURER
212+
&& dev.product == usb::PRODUCT
213+
&& (dev.serial_number == filter_serial || filter_serial == "-")
214+
&& dev.class_code == usb::CLASS
215+
&& dev.sub_class_code == usb::SUB_CLASS
216+
&& dev.protocol_code == usb::PROTOCOL
217+
&& iface.class_code == usb::INTERFACE_CLASS
218+
&& iface.sub_class_code == usb::INTERFACE_SUB_CLASS
219+
&& iface.protocol_code == usb::INTERFACE_PROTOCOL
220+
&& iface.description == filter_interface_name
221+
};
222+
223+
match aggligator_util::transport::usb::UsbConnector::new(filter) {
224+
Ok(c) => Some(c),
225+
Err(err) => {
226+
eprintln!("cannot use USB target: {err}");
227+
None
228+
}
229+
}
230+
}
231+
None => None,
232+
}
233+
};
234+
170235
if targets.is_empty() {
171236
bail!("No connection transports.");
172237
}
@@ -195,6 +260,8 @@ impl ClientCli {
195260
let tcp_connector = tcp_connector.clone();
196261
#[cfg(feature = "rfcomm")]
197262
let rfcomm_connector = rfcomm_connector.clone();
263+
#[cfg(feature = "usb-host")]
264+
let usb_connector = usb_connector.clone();
198265
let dump = dump.clone();
199266
port_tasks.push(async move {
200267
loop {
@@ -215,6 +282,10 @@ impl ClientCli {
215282
if let Some(c) = rfcomm_connector.clone() {
216283
connector.add(c);
217284
}
285+
#[cfg(feature = "usb-host")]
286+
if let Some(c) = usb_connector() {
287+
connector.add(c);
288+
}
218289
let control = connector.control();
219290
let outgoing = connector.channel().unwrap();
220291

@@ -317,6 +388,14 @@ pub struct ServerCli {
317388
#[cfg(feature = "rfcomm")]
318389
#[arg(long)]
319390
rfcomm: Option<u8>,
391+
/// Listen on USB device controller (UDC).
392+
#[cfg(feature = "usb-device")]
393+
#[arg(long)]
394+
usb: bool,
395+
/// USB interface name.
396+
#[cfg(feature = "usb-host")]
397+
#[arg(long, default_value=usb::DEFAULT_INTERFACE_NAME)]
398+
usb_interface_name: String,
320399
}
321400

322401
impl ServerCli {
@@ -371,6 +450,51 @@ impl ServerCli {
371450
}
372451
}
373452

453+
#[cfg(feature = "usb-device")]
454+
let _usb_reg = if self.usb {
455+
fn register_usb(
456+
serial: &str, interface_name: &str,
457+
) -> Result<(usb_gadget::RegGadget, upc::device::UpcFunction, std::ffi::OsString)> {
458+
let udc = usb_gadget::default_udc()?;
459+
let udc_name = udc.name().to_os_string();
460+
461+
let (upc, func_hnd) = upc::device::UpcFunction::new(
462+
upc::device::InterfaceId::new(upc::Class::new(
463+
usb::INTERFACE_CLASS,
464+
usb::INTERFACE_SUB_CLASS,
465+
usb::INTERFACE_PROTOCOL,
466+
))
467+
.with_name(interface_name),
468+
);
469+
470+
let reg = usb_gadget::Gadget::new(
471+
usb_gadget::Class::new(usb::CLASS, usb::SUB_CLASS, usb::PROTOCOL),
472+
usb_gadget::Id::new(usb::VID, usb::PID),
473+
usb_gadget::Strings::new(usb::MANUFACTURER, usb::PRODUCT, serial),
474+
)
475+
.with_os_descriptor(usb_gadget::OsDescriptor::microsoft())
476+
.with_config(usb_gadget::Config::new("config").with_function(func_hnd))
477+
.bind(&udc)?;
478+
479+
Ok((reg, upc, udc_name))
480+
}
481+
482+
let serial = gethostname::gethostname().to_string_lossy().to_string();
483+
match register_usb(&serial, &self.usb_interface_name) {
484+
Ok((usb_reg, upc, udc_name)) => {
485+
acceptor.add(aggligator_util::transport::usb::UsbAcceptor::new(upc, &udc_name));
486+
server_ports.push(format!("UDC {} ({serial})", udc_name.to_string_lossy()));
487+
Some(usb_reg)
488+
}
489+
Err(err) => {
490+
eprintln!("Cannot listen on USB: {err}");
491+
None
492+
}
493+
}
494+
} else {
495+
None
496+
};
497+
374498
if server_ports.is_empty() {
375499
bail!("No listening transports.");
376500
}

aggligator-util/src/transport/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,10 @@ pub mod rfcomm;
349349
#[cfg_attr(docsrs, doc(cfg(feature = "rfcomm-profile")))]
350350
pub mod rfcomm_profile;
351351

352+
#[cfg(any(feature = "usb-host", feature = "usb-device"))]
353+
#[cfg_attr(docsrs, doc(cfg(any(feature = "usb-host", feature = "usb-device"))))]
354+
pub mod usb;
355+
352356
#[cfg(feature = "websocket")]
353357
#[cfg_attr(docsrs, doc(cfg(feature = "websocket")))]
354358
pub mod websocket;

0 commit comments

Comments
 (0)