├── .gitignore ├── Cargo.toml ├── examples ├── lsusb.rs └── monitor.rs └── src ├── lib.rs └── linux.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "usb-async" 3 | version = "0.1.0" 4 | authors = ["Dan Ravensloft "] 5 | license = "Apache-2.0 OR MIT" 6 | edition = "2018" 7 | 8 | [dependencies] 9 | futures = "0.1" 10 | mio = "0.6" 11 | tokio = "0.1" 12 | 13 | [target.'cfg(target_os = "linux")'.dependencies] 14 | udev = "0.2" 15 | -------------------------------------------------------------------------------- /examples/lsusb.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | 3 | use usb_async; 4 | 5 | fn main() -> Result<(), Box> { 6 | let ctx = usb_async::Context::new()?; 7 | 8 | for dev in ctx.connected_devices() { 9 | let vendor_id = ctx.vendor_id(dev).ok_or(usb_async::Error::NotConnected)?; 10 | let product_id = ctx.product_id(dev).ok_or(usb_async::Error::NotConnected)?; 11 | let manufacturer_string = ctx.manufacturer_string(dev)?; 12 | let product_string = ctx.product_string(dev)?; 13 | println!( 14 | "{:04x}:{:04x} {} {}", 15 | vendor_id, product_id, manufacturer_string, product_string 16 | ); 17 | } 18 | 19 | Ok(()) 20 | } 21 | -------------------------------------------------------------------------------- /examples/monitor.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | 3 | use tokio::{ 4 | prelude::*, 5 | runtime::current_thread, 6 | }; 7 | use usb_async; 8 | 9 | fn main() -> Result<(), Box> { 10 | let ctx = usb_async::Context::new()?; 11 | let mut mon = ctx.monitor()?.into_future(); 12 | let mut rt = current_thread::Runtime::new()?; 13 | loop { 14 | mon = match rt.block_on(mon) { 15 | Ok((event, chan)) => { 16 | match event { 17 | Some(usb_async::Event::Add(device)) => { 18 | let vendor_id = ctx.vendor_id(device).ok_or(usb_async::Error::NotConnected)?; 19 | let product_id = ctx.product_id(device).ok_or(usb_async::Error::NotConnected)?; 20 | 21 | println!("{:04x}:{:04x} was plugged in", vendor_id, product_id); 22 | }, 23 | Some(usb_async::Event::Remove(device)) => { 24 | let vendor_id = ctx.vendor_id(device).ok_or(usb_async::Error::NotConnected)?; 25 | let product_id = ctx.product_id(device).ok_or(usb_async::Error::NotConnected)?; 26 | 27 | println!("{:04x}:{:04x} was unplugged", vendor_id, product_id); 28 | }, 29 | None => return Ok(()) 30 | }; 31 | chan 32 | } 33 | Err((err, _)) => return Err(Box::new(err)), 34 | } 35 | .into_future() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Future-based USB interface. 2 | #![deny(missing_docs)] 3 | #![allow(clippy::cast_possible_truncation)] 4 | 5 | use std::{ 6 | cell::RefCell, 7 | convert::TryFrom, 8 | error::Error as StdError, 9 | fmt, 10 | io, 11 | }; 12 | 13 | use tokio::prelude::*; 14 | 15 | #[cfg(target_os = "linux")] 16 | #[path = "linux.rs"] 17 | mod os; 18 | 19 | #[cfg(not(target_os = "linux"))] 20 | compile_error!("Sorry, usb-async has not been ported to your platform yet."); 21 | 22 | /// USB errors. 23 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] 24 | pub enum Error { 25 | /// An invalid ID was passed as argument. 26 | InvalidId, 27 | /// The device the ID refers to is not connected. 28 | NotConnected, 29 | /// An io::Error occurred. 30 | Io(io::ErrorKind), 31 | } 32 | 33 | impl From for Error { 34 | fn from(err: os::UsbError) -> Self { 35 | match err { 36 | os::UsbError::InvalidId => Error::InvalidId, 37 | os::UsbError::NotConnected => Error::NotConnected, 38 | os::UsbError::Io(io) => Error::Io(io), 39 | } 40 | } 41 | } 42 | 43 | impl fmt::Display for Error { 44 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 45 | match self { 46 | Error::InvalidId => write!(f, "an invalid device was specified"), 47 | Error::NotConnected => write!(f, "the specified device is not connected"), 48 | Error::Io(io) => write!(f, "an io error occurred: {:?}", io), 49 | } 50 | } 51 | } 52 | 53 | impl StdError for Error {} 54 | 55 | /// A handle to a USB device. 56 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] 57 | pub struct Id(os::Id); 58 | 59 | impl From for os::Id { 60 | fn from(id: Id) -> Self { 61 | id.0 62 | } 63 | } 64 | 65 | impl From for Id { 66 | fn from(id: os::Id) -> Self { 67 | Self(id) 68 | } 69 | } 70 | 71 | /// A USB hotplug event. 72 | #[derive(Copy, Clone, Debug, PartialEq, Hash)] 73 | pub enum Event { 74 | /// A USB device was plugged in. 75 | Add(Id), 76 | /// A USB device was removed. 77 | Remove(Id), 78 | } 79 | 80 | impl TryFrom for Event { 81 | type Error = (); 82 | 83 | fn try_from(event: os::Event) -> Result { 84 | match event { 85 | os::Event::Add(id) => Ok(Event::Add(id.into())), 86 | os::Event::Remove(id) => Ok(Event::Remove(id.into())), 87 | os::Event::Change(_) | os::Event::Unknown => Err(()), 88 | } 89 | } 90 | } 91 | 92 | /// A USB hotplug event monitor. 93 | pub struct HotplugMonitor<'a> { 94 | monitor: os::Monitor<'a>, 95 | context: &'a Context, 96 | } 97 | 98 | impl Stream for HotplugMonitor<'_> { 99 | type Item = Event; 100 | type Error = Error; 101 | 102 | fn poll(&mut self) -> Result>, Error> { 103 | match self.monitor.poll() { 104 | Ok(Async::Ready(Some(ev))) => { 105 | match Event::try_from(ev) { 106 | Ok(ev) => { 107 | match ev { 108 | Event::Add(id) => { 109 | self.context.add(id); 110 | Ok(Async::Ready(Some(Event::Add(id)))) 111 | }, 112 | Event::Remove(id) => { 113 | Ok(Async::Ready(Some(Event::Remove(id)))) 114 | }, 115 | } 116 | }, 117 | // Drop messages we don't understand. 118 | Err(()) => Ok(Async::NotReady), 119 | } 120 | } 121 | Ok(Async::Ready(None)) => Ok(Async::Ready(None)), 122 | Ok(Async::NotReady) => Ok(Async::NotReady), 123 | Err(err) => Err(err.into()), 124 | } 125 | } 126 | } 127 | 128 | struct Metadata { 129 | vendor_id: Option, 130 | product_id: Option, 131 | } 132 | 133 | /// A USB context. 134 | pub struct Context { 135 | context: os::Context, 136 | metadata: RefCell>, 137 | } 138 | 139 | impl Context { 140 | fn add(&self, id: Id) { 141 | let vendor_id = self.context.vendor_id(id.into()).ok(); 142 | let product_id = self.context.product_id(id.into()).ok(); 143 | let metadata = Metadata { 144 | vendor_id, 145 | product_id, 146 | }; 147 | self.metadata.borrow_mut().push(metadata); 148 | } 149 | 150 | /// Create a USB context. 151 | pub fn new() -> Result> { 152 | let context = Self { 153 | context: os::Context::new()?, 154 | metadata: RefCell::new(Vec::new()), 155 | }; 156 | 157 | for dev in context.devices() { 158 | context.add(dev); 159 | } 160 | 161 | Ok(context) 162 | } 163 | 164 | /// Create a USB hotplug monitor. 165 | pub fn monitor(&self) -> Result, Box> { 166 | Ok(HotplugMonitor { 167 | monitor: self.context.monitor()?, 168 | context: self, 169 | }) 170 | } 171 | 172 | /// Is a device plugged in? 173 | pub fn is_connected(&self, id: Id) -> bool { 174 | self.context.is_connected(id.into()) 175 | } 176 | 177 | /// Retrieve the USB vendor ID of a device. 178 | pub fn vendor_id(&self, id: Id) -> Option { 179 | self.metadata.borrow()[(id.0).0 as usize].vendor_id 180 | } 181 | 182 | /// Retrieve the USB product ID of a device. 183 | pub fn product_id(&self, id: Id) -> Option { 184 | self.metadata.borrow()[(id.0).0 as usize].product_id 185 | } 186 | 187 | /// Retrieve the USB manufacturer string of a device. 188 | pub fn manufacturer_string(&self, id: Id) -> Result { 189 | self.context 190 | .manufacturer_string(id.into()) 191 | .map_err(std::convert::Into::into) 192 | } 193 | 194 | /// Retrieve the USB product string of a device. 195 | pub fn product_string(&self, id: Id) -> Result { 196 | self.context 197 | .product_string(id.into()) 198 | .map_err(std::convert::Into::into) 199 | } 200 | 201 | /// Iterate through all devices, both connected and disconnected. 202 | /// 203 | /// Use `connected_devices` to only iterate over currently plugged in devices. 204 | pub fn devices(&self) -> impl Iterator { 205 | self.context.devices().map(Id) 206 | } 207 | 208 | /// Iterate through connected devices. 209 | pub fn connected_devices(&self) -> impl Iterator + '_ { 210 | self.devices().filter(move |id| self.is_connected(*id)) 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/linux.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cell::RefCell, 3 | error, io, 4 | os::unix::io::AsRawFd, 5 | path::{Path, PathBuf}, 6 | }; 7 | 8 | use udev; 9 | use mio; 10 | use tokio::{prelude::*, reactor}; 11 | 12 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] 13 | pub struct Id(pub u32); 14 | 15 | impl From for usize { 16 | fn from(id: Id) -> Self { 17 | let Id(id) = id; 18 | id as Self 19 | } 20 | } 21 | 22 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] 23 | pub enum Event { 24 | Add(Id), 25 | Remove(Id), 26 | Change(Id), 27 | Unknown, 28 | } 29 | 30 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] 31 | pub enum UsbError { 32 | InvalidId, 33 | NotConnected, 34 | Io(io::ErrorKind), 35 | } 36 | 37 | impl From for UsbError { 38 | fn from(err: udev::Error) -> Self { 39 | use udev::ErrorKind; 40 | match err.kind() { 41 | ErrorKind::NoMem => panic!("usb-async: error from udev: out of memory"), 42 | ErrorKind::InvalidInput => panic!("usb-async: error from udev: invalid input\nThis is probably a usb-async bug; please report it"), 43 | ErrorKind::Io(io_err) => UsbError::Io(io_err), 44 | } 45 | } 46 | } 47 | 48 | impl From for UsbError { 49 | fn from(err: io::Error) -> Self { 50 | UsbError::Io(err.kind()) 51 | } 52 | } 53 | 54 | pub struct Monitor<'a> { 55 | context: &'a Context, 56 | socket: udev::MonitorSocket, 57 | reg: reactor::Registration, 58 | } 59 | 60 | impl Stream for Monitor<'_> { 61 | type Item = Event; 62 | type Error = UsbError; // Can this actually fail? 63 | 64 | fn poll(&mut self) -> Result>, UsbError> { 65 | self.reg 66 | .register(&mio::unix::EventedFd(&self.socket.as_raw_fd()))?; 67 | 68 | match self.reg.poll_read_ready()? { 69 | Async::Ready(readiness) => { 70 | if readiness.is_readable() { 71 | if let Some(event) = self.socket.next() { 72 | let device = event.device(); 73 | let path = device.syspath(); 74 | println!("Got {} event on {}", event.device().property_value("ACTION").unwrap().to_str().unwrap(), path.display()); 75 | 76 | match event.event_type() { 77 | udev::EventType::Add => { 78 | match self.context.add_device(path) { 79 | Some(id) => Ok(Async::Ready(Some(Event::Add(id)))), 80 | None => Ok(Async::NotReady), 81 | } 82 | }, 83 | udev::EventType::Remove => { 84 | match self.context.remove_device_by_path(path) { 85 | Some(id) => Ok(Async::Ready(Some(Event::Remove(id)))), 86 | None => Ok(Async::NotReady), 87 | } 88 | }, 89 | udev::EventType::Change | udev::EventType::Unknown => { 90 | Ok(Async::NotReady) // For now 91 | } 92 | } 93 | } else { 94 | Ok(Async::NotReady) 95 | } 96 | } else { 97 | Ok(Async::NotReady) 98 | } 99 | } 100 | Async::NotReady => Ok(Async::NotReady), 101 | } 102 | } 103 | } 104 | 105 | pub struct Context { 106 | udev: udev::Context, 107 | paths: RefCell>>, 108 | } 109 | 110 | impl Context { 111 | pub fn new() -> Result> { 112 | let context = Self { 113 | udev: udev::Context::new()?, 114 | paths: RefCell::new(Vec::new()), 115 | }; 116 | 117 | { 118 | // Scan for currently connected devices. 119 | let mut enumerator = udev::Enumerator::new(&context.udev)?; 120 | enumerator.match_subsystem("usb")?; 121 | for dev in enumerator.scan_devices()? { 122 | let _ = context.add_device(dev.syspath()); 123 | } 124 | } 125 | 126 | Ok(context) 127 | } 128 | 129 | pub fn monitor(&self) -> Result, Box> { 130 | let mut monitor = udev::MonitorBuilder::new(&self.udev)?; 131 | monitor.match_subsystem("usb")?; 132 | Ok(Monitor { 133 | context: self, 134 | socket: monitor.listen()?, 135 | reg: reactor::Registration::new(), 136 | }) 137 | } 138 | 139 | fn add_device(&self, path: &Path) -> Option { 140 | let dev = self.udev.device_from_syspath(path).ok()?; 141 | let _ = dev.attribute_value("idVendor")?; 142 | self.paths.borrow_mut().push(Some(path.to_path_buf())); 143 | Some(Id((self.paths.borrow().len() - 1) as u32)) 144 | } 145 | 146 | fn remove_device_by_path(&self, path: &Path) -> Option { 147 | match self 148 | .paths 149 | .borrow_mut() 150 | .iter_mut() 151 | .enumerate() 152 | .find(|(_, current)| current.as_ref().map_or(false, |current| current == path)) 153 | { 154 | Some((id, path)) => { 155 | *path = None; 156 | Some(Id(id as u32)) 157 | } 158 | None => None, 159 | } 160 | } 161 | 162 | // In case Event::Change is used, we'll need to look up Ids by Path. 163 | fn _find_device_by_path(&self, path: &Path) -> Option { 164 | self.paths 165 | .borrow() 166 | .iter() 167 | .enumerate() 168 | .find_map(|(id, current)| { 169 | current.as_ref().and_then(|current| { 170 | if current == path { 171 | Some(Id(id as u32)) 172 | } else { 173 | None 174 | } 175 | }) 176 | }) 177 | } 178 | 179 | fn id(&self, id: Id) -> Result { 180 | let id = id.into(); 181 | if id < self.paths.borrow().len() { 182 | let path: &Option = &self.paths.borrow()[id]; 183 | if path.is_some() { 184 | Ok(id) 185 | } else { 186 | Err(UsbError::NotConnected) 187 | } 188 | } else { 189 | Err(UsbError::InvalidId) 190 | } 191 | } 192 | 193 | pub fn is_connected(&self, id: Id) -> bool { 194 | self.id(id).is_ok() 195 | } 196 | 197 | fn udev_lookup_hex(&self, id: Id, attr: &str) -> Result { 198 | fn udev_attribute_walk(dev: &udev::Device, name: &str) -> Option { 199 | let attr = dev.attributes().find(|attr| attr.name() == name); 200 | if let Some(attr) = attr { 201 | let attr = attr.value()?.to_str()?; 202 | Some(u16::from_str_radix(attr, 16).ok()?) 203 | } else { 204 | udev_attribute_walk(&dev.parent()?, name) 205 | } 206 | } 207 | 208 | let id = self.id(id)?; 209 | 210 | // unwrap() is safe here because the above line would have propagated an Err if it was not 211 | // currently connected. 212 | let device = self.udev.device_from_syspath(self.paths.borrow()[id].as_ref().unwrap()).map_err(|_| { 213 | self.paths.borrow_mut()[id] = None; 214 | UsbError::NotConnected 215 | })?; 216 | udev_attribute_walk(&device, attr).ok_or_else(|| { 217 | self.paths.borrow_mut()[id] = None; 218 | UsbError::NotConnected 219 | }) 220 | } 221 | 222 | fn udev_lookup_string(&self, id: Id, attr: &str) -> Result { 223 | fn udev_attribute_walk<'a>(dev: &'a udev::Device, name: &str) -> Option { 224 | let attr = dev.attributes().find(|attr| attr.name() == name); 225 | if let Some(attr) = attr { 226 | Some(String::from(attr.value()?.to_str()?)) 227 | } else { 228 | udev_attribute_walk(&dev.parent()?, name) 229 | } 230 | } 231 | 232 | let id = self.id(id)?; 233 | 234 | let device = self.udev.device_from_syspath(self.paths.borrow()[id].as_ref().unwrap()).map_err(|_| { 235 | self.paths.borrow_mut()[id] = None; 236 | UsbError::NotConnected 237 | })?; 238 | udev_attribute_walk(&device, attr).ok_or_else(|| { 239 | self.paths.borrow_mut()[id] = None; 240 | UsbError::NotConnected 241 | }) 242 | } 243 | 244 | pub fn vendor_id(&self, id: Id) -> Result { 245 | self.udev_lookup_hex(id, "idVendor") 246 | } 247 | 248 | pub fn product_id(&self, id: Id) -> Result { 249 | self.udev_lookup_hex(id, "idProduct") 250 | } 251 | 252 | pub fn manufacturer_string(&self, id: Id) -> Result { 253 | self.udev_lookup_string(id, "manufacturer") 254 | } 255 | 256 | pub fn product_string(&self, id: Id) -> Result { 257 | self.udev_lookup_string(id, "product") 258 | } 259 | 260 | pub fn devices(&self) -> impl Iterator { 261 | (0..(self.paths.borrow().len())).map(|id| Id(id as u32)) 262 | } 263 | } 264 | --------------------------------------------------------------------------------