#![warn(missing_docs)]
use std::{
fmt,
marker::PhantomData,
pin::Pin,
task::{ready, Context, Poll},
};
use futures_core::Stream;
use futures_util::StreamExt;
use js_sys::{Reflect, Uint8Array};
use tokio::sync::broadcast;
use tokio_stream::wrappers::{errors::BroadcastStreamRecvError, BroadcastStream};
use wasm_bindgen::{prelude::Closure, JsCast, JsValue};
use wasm_bindgen_futures::{spawn_local, JsFuture};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Error {
kind: ErrorKind,
msg: String,
}
impl Error {
pub fn kind(&self) -> ErrorKind {
self.kind
}
pub fn msg(&self) -> &str {
&self.msg
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}: {}", self.kind, &self.msg)
}
}
impl std::error::Error for Error {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum ErrorKind {
Unsupported,
AlreadyOpen,
Disconnected,
Security,
Stall,
Babble,
Transfer,
InvalidAccess,
Other,
}
impl Error {
fn new(kind: ErrorKind, msg: impl AsRef<str>) -> Self {
Self { kind, msg: msg.as_ref().to_string() }
}
}
impl From<JsValue> for Error {
fn from(value: JsValue) -> Self {
if let Some(js_error) = value.dyn_ref::<js_sys::Error>() {
let msg = js_error.message().as_string().unwrap();
let kind = match js_error.name().as_string().unwrap().as_str() {
"NotFoundError" => ErrorKind::Disconnected,
"SecurityError" => ErrorKind::Security,
"InvalidAccessError" => ErrorKind::InvalidAccess,
"NetworkError" => ErrorKind::Transfer,
_ => ErrorKind::Other,
};
return Error::new(kind, msg);
}
let msg = value.as_string().unwrap_or_else(|| "unknown error".into());
Error::new(ErrorKind::Other, msg)
}
}
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct UsbConfiguration {
pub configuration_value: u8,
pub configuration_name: Option<String>,
pub interfaces: Vec<UsbInterface>,
}
impl From<&web_sys::UsbConfiguration> for UsbConfiguration {
fn from(conf: &web_sys::UsbConfiguration) -> Self {
let iface_list = conf.interfaces();
let mut interfaces = Vec::new();
for i in 0..iface_list.length() {
if let Some(iface) = iface_list.get(i).dyn_ref::<web_sys::UsbInterface>() {
interfaces.push(UsbInterface::from(iface));
}
}
Self {
configuration_value: conf.configuration_value(),
configuration_name: conf.configuration_name(),
interfaces,
}
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct UsbInterface {
pub interface_number: u8,
pub alternates: Vec<UsbAlternateSetting>,
}
impl From<&web_sys::UsbInterface> for UsbInterface {
fn from(iface: &web_sys::UsbInterface) -> Self {
let alt_list = iface.alternates();
let mut alternates = Vec::new();
for i in 0..alt_list.length() {
if let Some(alt) = alt_list.get(i).dyn_ref::<web_sys::UsbAlternateInterface>() {
alternates.push(UsbAlternateSetting::from(alt));
}
}
Self { interface_number: iface.interface_number(), alternates }
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct UsbAlternateSetting {
pub alternate_setting: u8,
pub interface_class: u8,
pub interface_subclass: u8,
pub interface_protocol: u8,
pub interface_name: Option<String>,
pub endpoints: Vec<UsbEndpoint>,
}
impl From<&web_sys::UsbAlternateInterface> for UsbAlternateSetting {
fn from(alt: &web_sys::UsbAlternateInterface) -> Self {
let ep_list = alt.endpoints();
let mut endpoints = Vec::new();
for i in 0..ep_list.length() {
if let Some(ep) = ep_list.get(i).dyn_ref::<web_sys::UsbEndpoint>() {
endpoints.push(UsbEndpoint::from(ep));
}
}
Self {
alternate_setting: alt.alternate_setting(),
interface_class: alt.interface_class(),
interface_subclass: alt.interface_subclass(),
interface_protocol: alt.interface_protocol(),
interface_name: alt.interface_name(),
endpoints,
}
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct UsbEndpoint {
pub endpoint_number: u8,
pub direction: UsbDirection,
pub endpoint_type: UsbEndpointType,
pub packet_size: u32,
}
impl From<&web_sys::UsbEndpoint> for UsbEndpoint {
fn from(ep: &web_sys::UsbEndpoint) -> Self {
Self {
endpoint_number: ep.endpoint_number(),
direction: ep.direction().into(),
endpoint_type: ep.type_().into(),
packet_size: ep.packet_size(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum UsbEndpointType {
Bulk,
Interrupt,
Isochronous,
}
impl From<web_sys::UsbEndpointType> for UsbEndpointType {
fn from(value: web_sys::UsbEndpointType) -> Self {
match value {
web_sys::UsbEndpointType::Bulk => Self::Bulk,
web_sys::UsbEndpointType::Interrupt => Self::Interrupt,
web_sys::UsbEndpointType::Isochronous => Self::Isochronous,
other => unreachable!("unsupported UsbEndpointType: {other:?}"),
}
}
}
#[derive(Clone)]
pub struct UsbDevice {
device: web_sys::UsbDevice,
}
impl UsbDevice {
pub fn vendor_id(&self) -> u16 {
self.device.vendor_id()
}
pub fn product_id(&self) -> u16 {
self.device.product_id()
}
pub fn device_class(&self) -> u8 {
self.device.device_class()
}
pub fn device_subclass(&self) -> u8 {
self.device.device_subclass()
}
pub fn device_protocol(&self) -> u8 {
self.device.device_protocol()
}
pub fn device_version_major(&self) -> u8 {
self.device.device_version_major()
}
pub fn device_version_minor(&self) -> u8 {
self.device.device_version_minor()
}
pub fn device_version_subminor(&self) -> u8 {
self.device.device_version_subminor()
}
pub fn manufacturer_name(&self) -> Option<String> {
self.device.manufacturer_name()
}
pub fn product_name(&self) -> Option<String> {
self.device.product_name()
}
pub fn serial_number(&self) -> Option<String> {
self.device.serial_number()
}
pub fn opened(&self) -> bool {
self.device.opened()
}
pub fn configuration(&self) -> Option<UsbConfiguration> {
self.device.configuration().map(|cfg| (&cfg).into())
}
pub fn configurations(&self) -> Vec<UsbConfiguration> {
let cfg_list = self.device.configurations();
let mut configurations = Vec::new();
for i in 0..cfg_list.length() {
if let Some(conf) = cfg_list.get(i).dyn_ref::<web_sys::UsbConfiguration>() {
configurations.push(UsbConfiguration::from(conf));
}
}
configurations
}
pub async fn forget(&self) {
JsFuture::from(self.device.forget()).await.unwrap();
}
pub async fn open(&self) -> Result<OpenUsbDevice> {
if self.opened() {
return Err(Error::new(ErrorKind::AlreadyOpen, "USB device is already open"));
}
JsFuture::from(self.device.open()).await?;
Ok(OpenUsbDevice { device: self.clone(), closed: false })
}
}
impl std::fmt::Debug for UsbDevice {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("UsbDevice")
.field("vendor_id", &self.vendor_id())
.field("product_id", &self.product_id())
.field("device_class", &self.device_class())
.field("device_subclass", &self.device_subclass())
.field("device_protocol", &self.device_protocol())
.field("device_version_major", &self.device_version_major())
.field("device_version_minor", &self.device_version_minor())
.field("device_version_subminor", &self.device_version_subminor())
.field("manufacturer_name", &self.manufacturer_name())
.field("product_name", &self.product_name())
.field("serial_number", &self.serial_number())
.field("opened", &self.opened())
.field("configuration", &self.configuration())
.field("configurations", &self.configurations())
.finish()
}
}
impl From<web_sys::UsbDevice> for UsbDevice {
fn from(device: web_sys::UsbDevice) -> Self {
Self { device }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum UsbDirection {
In,
Out,
}
impl From<web_sys::UsbDirection> for UsbDirection {
fn from(value: web_sys::UsbDirection) -> Self {
match value {
web_sys::UsbDirection::In => Self::In,
web_sys::UsbDirection::Out => Self::Out,
other => unreachable!("unsupported UsbDirection {other:?}"),
}
}
}
impl From<UsbDirection> for web_sys::UsbDirection {
fn from(direction: UsbDirection) -> Self {
match direction {
UsbDirection::In => web_sys::UsbDirection::In,
UsbDirection::Out => web_sys::UsbDirection::Out,
}
}
}
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct UsbDeviceFilter {
pub vendor_id: Option<u16>,
pub product_id: Option<u16>,
pub class_code: Option<u8>,
pub subclass_code: Option<u8>,
pub protocol_code: Option<u8>,
pub serial_number: Option<String>,
}
impl UsbDeviceFilter {
pub fn new() -> Self {
Self::default()
}
}
impl From<&UsbDeviceFilter> for web_sys::UsbDeviceFilter {
fn from(value: &UsbDeviceFilter) -> Self {
let filter = web_sys::UsbDeviceFilter::new();
if let Some(x) = value.vendor_id {
filter.set_vendor_id(x);
}
if let Some(x) = value.product_id {
filter.set_product_id(x);
}
if let Some(x) = value.class_code {
filter.set_class_code(x);
}
if let Some(x) = value.subclass_code {
filter.set_subclass_code(x);
}
if let Some(x) = value.protocol_code {
filter.set_protocol_code(x);
}
if let Some(x) = &value.serial_number {
filter.set_serial_number(x);
}
filter
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum UsbRecipient {
Device,
Interface,
Endpoint,
Other,
}
impl From<UsbRecipient> for web_sys::UsbRecipient {
fn from(recipient: UsbRecipient) -> Self {
match recipient {
UsbRecipient::Device => Self::Device,
UsbRecipient::Interface => Self::Interface,
UsbRecipient::Endpoint => Self::Endpoint,
UsbRecipient::Other => Self::Other,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum UsbRequestType {
Standard,
Class,
Vendor,
}
impl From<UsbRequestType> for web_sys::UsbRequestType {
fn from(req_type: UsbRequestType) -> Self {
match req_type {
UsbRequestType::Standard => Self::Standard,
UsbRequestType::Class => Self::Class,
UsbRequestType::Vendor => Self::Vendor,
}
}
}
#[derive(Clone, Debug)]
struct UsbDeviceRequestOptions {
pub filters: Vec<UsbDeviceFilter>,
}
impl UsbDeviceRequestOptions {
pub fn new(filters: impl IntoIterator<Item = UsbDeviceFilter>) -> Self {
Self { filters: filters.into_iter().collect() }
}
}
impl From<&UsbDeviceRequestOptions> for web_sys::UsbDeviceRequestOptions {
fn from(value: &UsbDeviceRequestOptions) -> Self {
let filters = js_sys::Array::new();
for filter in &value.filters {
filters.push(&web_sys::UsbDeviceFilter::from(filter));
}
web_sys::UsbDeviceRequestOptions::new(&filters)
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum UsbEvent {
Connected(UsbDevice),
Disconnected(UsbDevice),
}
#[derive(Debug, Clone)]
struct SendWrapper<T>(pub T);
unsafe impl<T> Send for SendWrapper<T> {}
pub struct UsbEvents {
rx: BroadcastStream<SendWrapper<UsbEvent>>,
_marker: PhantomData<*const ()>,
}
impl fmt::Debug for UsbEvents {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("UsbEvents").finish()
}
}
impl Stream for UsbEvents {
type Item = UsbEvent;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
loop {
match ready!(self.rx.poll_next_unpin(cx)) {
Some(Ok(event)) => break Poll::Ready(Some(event.0)),
Some(Err(BroadcastStreamRecvError::Lagged(_))) => (),
None => break Poll::Ready(None),
}
}
}
}
pub struct Usb {
usb: web_sys::Usb,
event_rx: broadcast::Receiver<SendWrapper<UsbEvent>>,
}
impl fmt::Debug for Usb {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("Usb").finish()
}
}
impl Usb {
pub fn new() -> Result<Self> {
let usb = Self::browser_usb()?;
let (event_tx, event_rx) = broadcast::channel(1024);
let on_connect = {
let event_tx = event_tx.clone();
Closure::wrap(Box::new(move |event: web_sys::UsbConnectionEvent| {
let _ = event_tx.send(SendWrapper(UsbEvent::Connected(event.device().into())));
}) as Box<dyn Fn(_)>)
};
usb.set_onconnect(Some(on_connect.into_js_value().unchecked_ref()));
let on_disconnect = {
let event_tx = event_tx.clone();
Closure::wrap(Box::new(move |event: web_sys::UsbConnectionEvent| {
let _ = event_tx.send(SendWrapper(UsbEvent::Disconnected(event.device().into())));
}) as Box<dyn Fn(_)>)
};
usb.set_ondisconnect(Some(on_disconnect.into_js_value().unchecked_ref()));
Ok(Self { usb, event_rx })
}
fn browser_usb() -> Result<web_sys::Usb> {
let global = js_sys::global();
if let Some(window) = global.dyn_ref::<web_sys::Window>() {
let navigator = window.navigator();
match Reflect::get(&navigator, &JsValue::from_str("usb")) {
Ok(usb) if !usb.is_null() && !usb.is_undefined() => return Ok(navigator.usb()),
_ => (),
}
}
if let Some(worker) = global.dyn_ref::<web_sys::WorkerGlobalScope>() {
let navigator = worker.navigator();
match Reflect::get(&navigator, &JsValue::from_str("usb")) {
Ok(usb) if !usb.is_null() && !usb.is_undefined() => return Ok(navigator.usb()),
_ => (),
}
}
Err(Error::new(ErrorKind::Unsupported, "browser does not support WebUSB"))
}
pub fn events(&self) -> UsbEvents {
UsbEvents { rx: self.event_rx.resubscribe().into(), _marker: PhantomData }
}
pub async fn devices(&self) -> Vec<UsbDevice> {
let list = JsFuture::from(self.usb.get_devices()).await.unwrap();
js_sys::Array::from(&list)
.iter()
.map(|dev| UsbDevice::from(dev.dyn_into::<web_sys::UsbDevice>().unwrap()))
.collect()
}
pub async fn request_device(&self, filters: impl IntoIterator<Item = UsbDeviceFilter>) -> Result<UsbDevice> {
let opts = &UsbDeviceRequestOptions::new(filters);
let dev = JsFuture::from(self.usb.request_device(&opts.into())).await?;
Ok(dev.dyn_into::<web_sys::UsbDevice>().unwrap().into())
}
}
impl Drop for Usb {
fn drop(&mut self) {
self.usb.set_onconnect(None);
self.usb.set_ondisconnect(None);
}
}
pub struct OpenUsbDevice {
device: UsbDevice,
closed: bool,
}
impl fmt::Debug for OpenUsbDevice {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("OpenUsbDevice").field("device", &self.device).finish()
}
}
impl AsRef<UsbDevice> for OpenUsbDevice {
fn as_ref(&self) -> &UsbDevice {
&self.device
}
}
impl OpenUsbDevice {
fn dev(&self) -> &web_sys::UsbDevice {
&self.device.device
}
pub fn device(&self) -> &UsbDevice {
&self.device
}
pub async fn close(mut self) -> Result<()> {
self.closed = true;
JsFuture::from(self.dev().close()).await?;
Ok(())
}
pub async fn reset(&self) -> Result<()> {
JsFuture::from(self.dev().reset()).await?;
Ok(())
}
pub async fn select_configuration(&self, configuration: u8) -> Result<()> {
JsFuture::from(self.dev().select_configuration(configuration)).await?;
Ok(())
}
pub async fn claim_interface(&self, interface: u8) -> Result<()> {
JsFuture::from(self.dev().claim_interface(interface)).await?;
Ok(())
}
pub async fn release_interface(&self, interface: u8) -> Result<()> {
JsFuture::from(self.dev().release_interface(interface)).await?;
Ok(())
}
pub async fn select_alternate_interface(&self, interface: u8, alternate: u8) -> Result<()> {
JsFuture::from(self.dev().select_alternate_interface(interface, alternate)).await?;
Ok(())
}
pub async fn clear_halt(&self, direction: UsbDirection, endpoint: u8) -> Result<()> {
JsFuture::from(self.dev().clear_halt(direction.into(), endpoint)).await?;
Ok(())
}
fn check_status(status: web_sys::UsbTransferStatus) -> Result<()> {
match status {
web_sys::UsbTransferStatus::Ok => Ok(()),
web_sys::UsbTransferStatus::Stall => Err(Error::new(ErrorKind::Stall, "stall condition")),
web_sys::UsbTransferStatus::Babble => Err(Error::new(ErrorKind::Babble, "device babbled")),
other => unreachable!("unsupported UsbTransferStatus {other:?}"),
}
}
pub async fn control_transfer_in(
&self, recipient: UsbRecipient, request_type: UsbRequestType, request: u8, value: u16, index: u16,
len: u16,
) -> Result<Vec<u8>> {
let setup = web_sys::UsbControlTransferParameters::new(
index,
recipient.into(),
request,
request_type.into(),
value,
);
let res = JsFuture::from(self.dev().control_transfer_in(&setup, len)).await?;
let res = res.dyn_into::<web_sys::UsbInTransferResult>().unwrap();
Self::check_status(res.status())?;
let data = Uint8Array::new(&res.data().unwrap().buffer()).to_vec();
Ok(data)
}
pub async fn control_transfer_out(
&self, recipient: UsbRecipient, request_type: UsbRequestType, request: u8, value: u16, index: u16,
data: &[u8],
) -> Result<u32> {
let setup = web_sys::UsbControlTransferParameters::new(
index,
recipient.into(),
request,
request_type.into(),
value,
);
let data = Uint8Array::from(data);
let res = JsFuture::from(self.dev().control_transfer_out_with_u8_array(&setup, &data)?).await?;
let res = res.dyn_into::<web_sys::UsbOutTransferResult>().unwrap();
Self::check_status(res.status())?;
Ok(res.bytes_written())
}
pub async fn isochronous_transfer_in(
&self, endpoint: u8, packet_lens: impl IntoIterator<Item = u32>,
) -> Result<Vec<Result<Vec<u8>>>> {
let array: js_sys::Array = packet_lens.into_iter().map(|len| JsValue::from_f64(len as _)).collect();
let res = JsFuture::from(self.dev().isochronous_transfer_in(endpoint, &array)).await?;
let res = res.dyn_into::<web_sys::UsbIsochronousInTransferResult>().unwrap();
let mut results = Vec::new();
for packet in res.packets() {
let packet = packet.dyn_into::<web_sys::UsbIsochronousInTransferPacket>().unwrap();
let result = match Self::check_status(packet.status()) {
Ok(()) => Ok(Uint8Array::new(&res.data().unwrap().buffer()).to_vec()),
Err(err) => Err(err),
};
results.push(result);
}
Ok(results)
}
pub async fn isochronous_transfer_out(
&self, endpoint: u8, packets: impl IntoIterator<Item = &[u8]>,
) -> Result<Vec<Result<u32>>> {
let mut data = Vec::new();
let mut lens = Vec::new();
for packet in packets {
data.extend_from_slice(packet);
lens.push(data.len());
}
let data = Uint8Array::from(&data[..]);
let lens: js_sys::Array = lens.into_iter().map(|len| JsValue::from_f64(len as _)).collect();
let res =
JsFuture::from(self.dev().isochronous_transfer_out_with_u8_array(endpoint, &data, &lens)?).await?;
let res = res.dyn_into::<web_sys::UsbIsochronousOutTransferResult>().unwrap();
let mut results = Vec::new();
for packet in res.packets() {
let packet = packet.dyn_into::<web_sys::UsbIsochronousOutTransferPacket>().unwrap();
let result = match Self::check_status(packet.status()) {
Ok(()) => Ok(packet.bytes_written()),
Err(err) => Err(err),
};
results.push(result);
}
Ok(results)
}
pub async fn transfer_in(&self, endpoint: u8, len: u32) -> Result<Vec<u8>> {
let res = JsFuture::from(self.dev().transfer_in(endpoint, len)).await?;
let res = res.dyn_into::<web_sys::UsbInTransferResult>().unwrap();
Self::check_status(res.status())?;
let data = Uint8Array::new(&res.data().unwrap().buffer()).to_vec();
Ok(data)
}
pub async fn transfer_out(&self, endpoint: u8, data: &[u8]) -> Result<u32> {
let data = Uint8Array::from(data);
let res = JsFuture::from(self.dev().transfer_out_with_u8_array(endpoint, &data)?).await?;
let res = res.dyn_into::<web_sys::UsbOutTransferResult>().unwrap();
Self::check_status(res.status())?;
Ok(res.bytes_written())
}
}
impl Drop for OpenUsbDevice {
fn drop(&mut self) {
if !self.closed {
let device = self.dev().clone();
let fut = JsFuture::from(device.close());
spawn_local(async move {
let _ = fut.await;
});
}
}
}